From 641477d6f62b44b24188da1884a28da7fd4896da Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Fri, 14 Jan 2022 23:16:05 +0100 Subject: [PATCH] add @requirezp and allow str/array to be on zp (with warning) --- .../target/c128/C128MachineDefinition.kt | 1 - .../target/c64/C64MachineDefinition.kt | 1 - .../codegen/target/cpu6502/codegen/AsmGen.kt | 179 +++++++++++------- .../target/cpu6502/codegen/ForLoopsAsmGen.kt | 17 +- .../target/cx16/CX16MachineDefinition.kt | 27 +-- .../compiler/astprocessing/AstChecker.kt | 8 - compiler/test/ZeropageTests.kt | 83 ++++---- compiler/test/ast/TestProg8Parser.kt | 14 +- .../src/prog8/ast/antlr/Antlr2Kotlin.kt | 63 ++---- compilerInterfaces/build.gradle | 1 + compilerInterfaces/compilerInterfaces.iml | 1 + .../src/prog8/compilerinterface/CallGraph.kt | 2 + .../compilerinterface/IMachineDefinition.kt | 1 - .../src/prog8/compilerinterface/Zeropage.kt | 59 ++++-- docs/source/programming.rst | 4 +- docs/source/syntaxreference.rst | 3 +- docs/source/todo.rst | 6 +- examples/test.p8 | 68 ++++--- parser/antlr/Prog8ANTLR.g4 | 14 +- 19 files changed, 310 insertions(+), 242 deletions(-) diff --git a/codeGeneration/src/prog8/codegen/target/c128/C128MachineDefinition.kt b/codeGeneration/src/prog8/codegen/target/c128/C128MachineDefinition.kt index db24c3624..602c9944f 100644 --- a/codeGeneration/src/prog8/codegen/target/c128/C128MachineDefinition.kt +++ b/codeGeneration/src/prog8/codegen/target/c128/C128MachineDefinition.kt @@ -58,7 +58,6 @@ class C128MachineDefinition: IMachineDefinition { } override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu - override fun getPreallocatedZeropageVars(): Map> = emptyMap() override fun initializeZeropage(compilerOptions: CompilationOptions) { zeropage = C128Zeropage(compilerOptions) diff --git a/codeGeneration/src/prog8/codegen/target/c64/C64MachineDefinition.kt b/codeGeneration/src/prog8/codegen/target/c64/C64MachineDefinition.kt index a341ceddf..c09f6469c 100644 --- a/codeGeneration/src/prog8/codegen/target/c64/C64MachineDefinition.kt +++ b/codeGeneration/src/prog8/codegen/target/c64/C64MachineDefinition.kt @@ -57,7 +57,6 @@ class C64MachineDefinition: IMachineDefinition { } override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu - override fun getPreallocatedZeropageVars(): Map> = emptyMap() override fun initializeZeropage(compilerOptions: CompilationOptions) { zeropage = C64Zeropage(compilerOptions) diff --git a/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/AsmGen.kt b/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/AsmGen.kt index 4502ee4d7..6ae1271e1 100644 --- a/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/AsmGen.kt +++ b/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/AsmGen.kt @@ -1,6 +1,7 @@ package prog8.codegen.target.cpu6502.codegen import com.github.michaelbull.result.fold +import com.github.michaelbull.result.onFailure import prog8.ast.* import prog8.ast.antlr.escape import prog8.ast.base.* @@ -42,7 +43,6 @@ class AsmGen(private val program: Program, private val assemblyLines = mutableListOf() private val globalFloatConsts = mutableMapOf() // all float values in the entire program (value -> varname) - private val allocatedZeropageVariables = compTarget.machine.getPreallocatedZeropageVars().toMutableMap() private val breakpointLabels = mutableListOf() private val forloopsAsmGen = ForLoopsAsmGen(program, this) private val postincrdecrAsmGen = PostIncrDecrAsmGen(program, this) @@ -69,37 +69,83 @@ class AsmGen(private val program: Program, if(allBlocks.first().name != "main") throw AssemblyError("first block should be 'main'") - for(b in program.allBlocks) { - block2asm(b) - } + allocateAllZeropageVariables() + if(errors.noErrors()) { + program.allBlocks.forEach { block2asm(it) } - for(removal in removals.toList()) { - removal.second.remove(removal.first) - removals.remove(removal) - } - - slaballocations() - footer() - - val output = outputDir.resolve("${program.name}.asm") - if(options.optimize) { - val separateLines = assemblyLines.flatMapTo(mutableListOf()) { it.split('\n') } - assemblyLines.clear() - while(optimizeAssembly(separateLines, options.compTarget.machine, program)>0) { - // optimize the assembly source code + for(removal in removals.toList()) { + removal.second.remove(removal.first) + removals.remove(removal) + } + + slaballocations() + footer() + + val output = outputDir.resolve("${program.name}.asm") + if(options.optimize) { + val separateLines = assemblyLines.flatMapTo(mutableListOf()) { it.split('\n') } + assemblyLines.clear() + while(optimizeAssembly(separateLines, options.compTarget.machine, program)>0) { + // optimize the assembly source code + } + output.writeLines(separateLines) + } else { + output.writeLines(assemblyLines) } - output.writeLines(separateLines) - } else { - output.writeLines(assemblyLines) } return if(errors.noErrors()) AssemblyProgram(true, program.name, outputDir, compTarget.name) else { + errors.report() AssemblyProgram(false, "", outputDir, compTarget.name) } } + private fun allocateAllZeropageVariables() { + if(options.zeropage==ZeropageType.DONTUSE) + return + val allVariables = this.callGraph.allIdentifiers.asSequence() + .map { it.value } + .filterIsInstance() + .filter { it.type==VarDeclType.VAR } + .toSet() + .map { it to it.scopedName.joinToString(".") } + val varsRequiringZp = allVariables.filter { it.first.zeropage==ZeropageWish.REQUIRE_ZEROPAGE } + val varsPreferringZp = allVariables + .filter { it.first.zeropage==ZeropageWish.PREFER_ZEROPAGE } + .sortedBy { options.compTarget.memorySize(it.first.datatype) } // allocate the smallest DT first + + for ((vardecl, scopedname) in varsRequiringZp) { + val arraySize: Int? = when(vardecl.datatype) { + DataType.STR -> { + (vardecl.value as StringLiteralValue).value.length + } + in ArrayDatatypes -> { + vardecl.arraysize!!.constIndex() + } + else -> null + } + val result = zeropage.allocate(scopedname, vardecl.datatype, arraySize, vardecl.position, errors) + result.onFailure { errors.err(it.message!!, vardecl.position) } + } + if(errors.noErrors()) { + varsPreferringZp.forEach { (vardecl, scopedname) -> + val arraySize: Int? = when (vardecl.datatype) { + DataType.STR -> { + (vardecl.value as StringLiteralValue).value.length + } + in ArrayDatatypes -> { + vardecl.arraysize!!.constIndex() + } + else -> null + } + zeropage.allocate(scopedname, vardecl.datatype, arraySize, vardecl.position, errors) + // no need to check for error, if there is one, just allocate in normal system ram later. + } + } + } + internal fun isTargetCpu(cpu: CpuType) = compTarget.machine.cpu == cpu internal fun haveFPWR() = compTarget is Cx16Target @@ -287,23 +333,28 @@ class AsmGen(private val program: Program, if(blockname=="prog8_lib" && variable.name.startsWith("P8ZP_SCRATCH_")) continue // the "hooks" to the temp vars are not generated as new variables val fullName = variable.scopedName.joinToString(".") - val zpVar = allocatedZeropageVariables[fullName] - if(zpVar==null) { - // This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space) + val zpAlloc = zeropage.allocatedZeropageVariable(fullName) + if (zpAlloc == null) { + // This var is not on the ZP yet. Attempt to move it there if it's an integer type if(variable.zeropage != ZeropageWish.NOT_IN_ZEROPAGE && - variable.datatype in zeropage.allowedDatatypes - && variable.datatype != DataType.FLOAT - && options.zeropage != ZeropageType.DONTUSE) { - try { - val address = zeropage.allocate(fullName, variable.datatype, null, errors) - errors.report() - out("${variable.name} = $address\t; auto zp ${variable.datatype}") - // make sure we add the var to the set of zpvars for this block - allocatedZeropageVariables[fullName] = address to variable.datatype - } catch (x: ZeropageDepletedError) { - // leave it as it is. - } + variable.datatype in IntegerDatatypes + && options.zeropage != ZeropageType.DONTUSE) { + val result = zeropage.allocate(fullName, variable.datatype, null, null, errors) + errors.report() + result.fold( + success = { address -> out("${variable.name} = $address\t; zp ${variable.datatype}") }, + failure = { /* leave it as it is, not on zeropage. */ } + ) } + } else { + // Var has been placed in ZP, just output the address + val lenspec = when(zpAlloc.second) { + DataType.FLOAT, + DataType.STR, + in ArrayDatatypes -> " ${zpAlloc.first.second} bytes" + else -> "" + } + out("${variable.name} = ${zpAlloc.first.first}\t; zp ${variable.datatype} $lenspec") } } } @@ -339,9 +390,7 @@ class AsmGen(private val program: Program, } } DataType.STR -> { - throw AssemblyError("all string vars should have been interned") -// val str = decl.value as StringLiteralValue -// outputStringvar(decl, compTarget.encodeString(str.value, str.altEncoding).plus(0.toUByte())) + throw AssemblyError("all string vars should have been interned into prog") } DataType.ARRAY_UB -> { val data = makeArrayFillDataUnsigned(decl) @@ -431,7 +480,11 @@ class AsmGen(private val program: Program, private fun vardecls2asm(statements: List, inBlock: Block?) { out("\n; non-zeropage variables") - val vars = statements.asSequence().filterIsInstance().filter { it.type==VarDeclType.VAR } + val vars = statements.asSequence() + .filterIsInstance() + .filter { + it.type==VarDeclType.VAR && zeropage.allocatedZeropageVariable(it.scopedName.joinToString("."))==null + } val encodedstringVars = vars .filter { it.datatype == DataType.STR && shouldActuallyOutputStringVar(it) } @@ -448,7 +501,7 @@ class AsmGen(private val program: Program, vars.filter{ it.datatype != DataType.STR }.sortedBy { it.datatype }.forEach { val scopedname = it.scopedName.joinToString(".") - if(scopedname !in allocatedZeropageVariables) { + if(!isZpVar(scopedname)) { if(blockname!="prog8_lib" || !it.name.startsWith("P8ZP_SCRATCH_")) // the "hooks" to the temp vars are not generated as new variables vardecl2asm(it) } @@ -1410,31 +1463,27 @@ $repeatLabel lda $counterVar val counterVar = makeLabel("counter") when(dt) { - DataType.UBYTE -> { - if(zeropage.hasByteAvailable()) { - // allocate count var on ZP - val zpAddr = zeropage.allocate(counterVar, DataType.UBYTE, stmt.position, errors) - asmInfo.extraVars.add(Triple(DataType.UBYTE, counterVar, zpAddr)) - } else { - if(mustBeInZeropage) - return null - asmInfo.extraVars.add(Triple(DataType.UBYTE, counterVar, null)) - } - } - DataType.UWORD -> { - if(zeropage.hasWordAvailable()) { - // allocate count var on ZP - val zpAddr = zeropage.allocate(counterVar, DataType.UWORD, stmt.position, errors) - asmInfo.extraVars.add(Triple(DataType.UWORD, counterVar, zpAddr)) - } else { - if(mustBeInZeropage) - return null - asmInfo.extraVars.add(Triple(DataType.UWORD, counterVar, null)) - } + DataType.UBYTE, DataType.UWORD -> { + val result = zeropage.allocate(counterVar, dt, null, stmt.position, errors) + return result.fold( + success = { zpvar -> + asmInfo.extraVars.add(Triple(dt, counterVar, zpvar.first)) + counterVar + }, + failure = { zpError -> + if(mustBeInZeropage) { + errors.err(zpError.message!!, stmt.position) + null + } else { + // allocate normally + asmInfo.extraVars.add(Triple(dt, counterVar, null)) + counterVar + } + } + ) } else -> throw AssemblyError("invalidt dt") } - return counterVar } private fun translate(stmt: When) { @@ -1689,11 +1738,11 @@ $label nop""") } internal fun isZpVar(scopedName: String): Boolean = - scopedName in allocatedZeropageVariables + zeropage.allocatedZeropageVariable(scopedName)!=null internal fun isZpVar(variable: IdentifierReference): Boolean { val vardecl = variable.targetVarDecl(program)!! - return vardecl.scopedName.joinToString(".") in allocatedZeropageVariables + return zeropage.allocatedZeropageVariable(vardecl.scopedName.joinToString("."))!=null } internal fun jmp(asmLabel: String) { diff --git a/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/ForLoopsAsmGen.kt b/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/ForLoopsAsmGen.kt index 92aa8a172..e52b36839 100644 --- a/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/ForLoopsAsmGen.kt +++ b/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/ForLoopsAsmGen.kt @@ -1,5 +1,6 @@ package prog8.codegen.target.cpu6502.codegen +import com.github.michaelbull.result.fold import prog8.ast.Program import prog8.ast.base.ArrayToElementTypes import prog8.ast.base.DataType @@ -289,9 +290,13 @@ $loopLabel sty $indexVar beq $endLabel""") } if(length>=16 && asmgen.zeropage.hasByteAvailable()) { + // TODO don't check for byte avail first, just use allocate and handle error // allocate index var on ZP - val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors) - asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""") + val result = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors) + result.fold( + success = { zpAddr-> asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""") }, + failure = { /*TODO regular allocation */} + ) } else { asmgen.out(""" $indexVar .byte 0""") @@ -328,9 +333,13 @@ $loopLabel sty $indexVar beq $endLabel""") } if(length>=16 && asmgen.zeropage.hasByteAvailable()) { + // TODO don't check for byte avail first, just use allocate and handle error // allocate index var on ZP - val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors) - asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""") + val result = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors) + result.fold( + success = { zpAddr-> asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""") }, + failure = { /*TODO regular allocation */} + ) } else { asmgen.out(""" $indexVar .byte 0""") diff --git a/codeGeneration/src/prog8/codegen/target/cx16/CX16MachineDefinition.kt b/codeGeneration/src/prog8/codegen/target/cx16/CX16MachineDefinition.kt index 7321af06f..eaf7a2747 100644 --- a/codeGeneration/src/prog8/codegen/target/cx16/CX16MachineDefinition.kt +++ b/codeGeneration/src/prog8/codegen/target/cx16/CX16MachineDefinition.kt @@ -67,18 +67,21 @@ class CX16MachineDefinition: IMachineDefinition { } override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0x9f00u..0x9fffu - override fun getPreallocatedZeropageVars(): Map> { - val vars = mutableMapOf>() - for(reg in 0..15) { - vars["cx16.r${reg}"] = (2+reg*2).toUInt() to DataType.UWORD // cx16.r0 .. cx16.r15 - vars["cx16.r${reg}s"] = (2+reg*2).toUInt() to DataType.WORD // cx16.r0s .. cx16.r15s - vars["cx16.r${reg}L"] = (2+reg*2).toUInt() to DataType.UBYTE // cx16.r0L .. cx16.r15L - vars["cx16.r${reg}H"] = (3+reg*2).toUInt() to DataType.UBYTE // cx16.r0H .. cx16.r15H - vars["cx16.r${reg}sL"] = (2+reg*2).toUInt() to DataType.BYTE // cx16.r0sL .. cx16.r15sL - vars["cx16.r${reg}sH"] = (3+reg*2).toUInt() to DataType.BYTE // cx16.r0sH .. cx16.r15sH - } - return vars - } + + + // TODO integrate this in the internal list of allocated zp variables: +// override fun getPreallocatedZeropageVars(): Map> { +// val vars = mutableMapOf>() +// for(reg in 0..15) { +// vars["cx16.r${reg}"] = (2+reg*2).toUInt() to DataType.UWORD // cx16.r0 .. cx16.r15 +// vars["cx16.r${reg}s"] = (2+reg*2).toUInt() to DataType.WORD // cx16.r0s .. cx16.r15s +// vars["cx16.r${reg}L"] = (2+reg*2).toUInt() to DataType.UBYTE // cx16.r0L .. cx16.r15L +// vars["cx16.r${reg}H"] = (3+reg*2).toUInt() to DataType.UBYTE // cx16.r0H .. cx16.r15H +// vars["cx16.r${reg}sL"] = (2+reg*2).toUInt() to DataType.BYTE // cx16.r0sL .. cx16.r15sL +// vars["cx16.r${reg}sH"] = (3+reg*2).toUInt() to DataType.BYTE // cx16.r0sH .. cx16.r15sH +// } +// return vars +// } override fun initializeZeropage(compilerOptions: CompilationOptions) { zeropage = CX16Zeropage(compilerOptions) diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index c748186f8..53538a671 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -523,14 +523,6 @@ internal class AstChecker(private val program: Program, err("const modifier can only be used on numeric types (byte, word, float)") } - // @zp can only occur on integers - if(decl.datatype !in IntegerDatatypes) { - if(decl.zeropage==ZeropageWish.PREFER_ZEROPAGE) - errors.warn("this datatype can't be placed in zeropage", decl.position) - if(decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE) - errors.err("this datatype can't be placed in zeropage", decl.position) - } - // FLOATS enabled? if(!compilerOptions.floats && decl.datatype.oneOf(DataType.FLOAT, DataType.ARRAY_F) && decl.type!= VarDeclType.MEMORY) err("floating point used, but that is not enabled via options") diff --git a/compiler/test/ZeropageTests.kt b/compiler/test/ZeropageTests.kt index 3e40fbea3..39de96964 100644 --- a/compiler/test/ZeropageTests.kt +++ b/compiler/test/ZeropageTests.kt @@ -1,5 +1,10 @@ package prog8tests +import com.github.michaelbull.result.expect +import com.github.michaelbull.result.get +import com.github.michaelbull.result.getOrElse +import com.github.michaelbull.result.onFailure +import io.kotest.assertions.fail import io.kotest.assertions.throwables.shouldThrow import io.kotest.assertions.withClue import io.kotest.core.spec.style.FunSpec @@ -89,26 +94,29 @@ class TestC64Zeropage: FunSpec({ test("testNames") { val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target)) - zp.allocate("", DataType.UBYTE, null, errors) - zp.allocate("", DataType.UBYTE, null, errors) - zp.allocate("varname", DataType.UBYTE, null, errors) - shouldThrow { - zp.allocate("varname", DataType.UBYTE, null, errors) - } - zp.allocate("varname2", DataType.UBYTE, null, errors) + var result = zp.allocate("", DataType.UBYTE, null, null, errors) + result.onFailure { fail(it.toString()) } + result = zp.allocate("", DataType.UBYTE, null, null, errors) + result.onFailure { fail(it.toString()) } + result = zp.allocate("varname", DataType.UBYTE, null, null, errors) + result.onFailure { fail(it.toString()) } + result = zp.allocate("varname", DataType.UBYTE, null, null, errors) + result.onFailure { fail(it.toString()) } // TODO SHOULD ACTUALLY BE ERROR + result = zp.allocate("varname2", DataType.UBYTE, null, null, errors) + result.onFailure { fail(it.toString()) } } test("testZpFloatEnable") { val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target)) shouldThrow { - zp.allocate("", DataType.FLOAT, null, errors) + zp.allocate("", DataType.FLOAT, null, null, errors) } val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, C64Target)) shouldThrow { - zp2.allocate("", DataType.FLOAT, null, errors) + zp2.allocate("", DataType.FLOAT, null, null, errors) } val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target)) - zp3.allocate("", DataType.FLOAT, null, errors) + zp3.allocate("", DataType.FLOAT, null, null, errors) } test("testZpModesWithFloats") { @@ -131,7 +139,7 @@ class TestC64Zeropage: FunSpec({ println(zp.free) zp.availableBytes() shouldBe 0 shouldThrow { - zp.allocate("", DataType.BYTE, null, errors) + zp.allocate("", DataType.BYTE, null, null, errors) } } @@ -144,9 +152,9 @@ class TestC64Zeropage: FunSpec({ zp3.availableBytes() shouldBe 125 val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target)) zp4.availableBytes() shouldBe 239 - zp4.allocate("test", DataType.UBYTE, null, errors) + zp4.allocate("test", DataType.UBYTE, null, null, errors) zp4.availableBytes() shouldBe 238 - zp4.allocate("test2", DataType.UBYTE, null, errors) + zp4.allocate("test2", DataType.UBYTE, null, null, errors) zp4.availableBytes() shouldBe 237 } @@ -179,21 +187,21 @@ class TestC64Zeropage: FunSpec({ shouldThrow { // in regular zp there aren't 5 sequential bytes free - zp.allocate("", DataType.FLOAT, null, errors) + zp.allocate("", DataType.FLOAT, null, null, errors) } for (i in 0 until zp.availableBytes()) { - val loc = zp.allocate("", DataType.UBYTE, null, errors) - loc shouldBeGreaterThan 0u + val result = zp.allocate("", DataType.UBYTE, null, null, errors) + result.getOrElse { throw it } } zp.availableBytes() shouldBe 0 zp.hasByteAvailable() shouldBe false zp.hasWordAvailable() shouldBe false shouldThrow { - zp.allocate("", DataType.UBYTE, null, errors) + zp.allocate("", DataType.UBYTE, null, null, errors) } shouldThrow { - zp.allocate("", DataType.UWORD, null, errors) + zp.allocate("", DataType.UWORD, null, null, errors) } } @@ -202,23 +210,24 @@ class TestC64Zeropage: FunSpec({ zp.availableBytes() shouldBe 239 zp.hasByteAvailable() shouldBe true zp.hasWordAvailable() shouldBe true - val loc = zp.allocate("", DataType.UWORD, null, errors) + val result = zp.allocate("", DataType.UWORD, null, null, errors) + val loc = result.getOrElse { throw it } .first loc shouldBeGreaterThan 3u loc shouldNotBeIn zp.free val num = zp.availableBytes() / 2 for(i in 0..num-3) { - zp.allocate("", DataType.UWORD, null, errors) + zp.allocate("", DataType.UWORD, null, null, errors) } zp.availableBytes() shouldBe 5 shouldThrow { // can't allocate because no more sequential bytes, only fragmented - zp.allocate("", DataType.UWORD, null, errors) + zp.allocate("", DataType.UWORD, null, null, errors) } for(i in 0..4) { - zp.allocate("", DataType.UBYTE, null, errors) + zp.allocate("", DataType.UBYTE, null, null, errors) } zp.availableBytes() shouldBe 0 @@ -226,25 +235,25 @@ class TestC64Zeropage: FunSpec({ zp.hasWordAvailable() shouldBe false shouldThrow { // no more space - zp.allocate("", DataType.UBYTE, null, errors) + zp.allocate("", DataType.UBYTE, null, null, errors) } } test("testEfficientAllocation") { val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target)) zp.availableBytes() shouldBe 18 - zp.allocate("", DataType.WORD, null, errors) shouldBe 0x04u - zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x06u - zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x0au - zp.allocate("", DataType.UWORD, null, errors) shouldBe 0x9bu - zp.allocate("", DataType.UWORD, null, errors) shouldBe 0x9eu - zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xa5u - zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xb0u - zp.allocate("", DataType.UWORD, null, errors) shouldBe 0xbeu - zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x0eu - zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x92u - zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0x96u - zp.allocate("", DataType.UBYTE, null, errors) shouldBe 0xf9u + zp.allocate("", DataType.WORD, null, null, errors) shouldBe 0x04u + zp.allocate("", DataType.UBYTE, null, null, errors) shouldBe 0x06u + zp.allocate("", DataType.UBYTE, null, null, errors) shouldBe 0x0au + zp.allocate("", DataType.UWORD, null, null, errors) shouldBe 0x9bu + zp.allocate("", DataType.UWORD, null, null, errors) shouldBe 0x9eu + zp.allocate("", DataType.UWORD, null, null, errors) shouldBe 0xa5u + zp.allocate("", DataType.UWORD, null, null, errors) shouldBe 0xb0u + zp.allocate("", DataType.UWORD, null, null, errors) shouldBe 0xbeu + zp.allocate("", DataType.UBYTE, null, null, errors) shouldBe 0x0eu + zp.allocate("", DataType.UBYTE, null, null, errors) shouldBe 0x92u + zp.allocate("", DataType.UBYTE, null, null, errors) shouldBe 0x96u + zp.allocate("", DataType.UBYTE, null, null, errors) shouldBe 0xf9u zp.availableBytes() shouldBe 0 } @@ -274,9 +283,9 @@ class TestCx16Zeropage: FunSpec({ zp2.availableBytes() shouldBe 175 val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target)) zp3.availableBytes() shouldBe 216 - zp3.allocate("test", DataType.UBYTE, null, errors) + zp3.allocate("test", DataType.UBYTE, null, null, errors) zp3.availableBytes() shouldBe 215 - zp3.allocate("test2", DataType.UBYTE, null, errors) + zp3.allocate("test2", DataType.UBYTE, null, null, errors) zp3.availableBytes() shouldBe 214 } diff --git a/compiler/test/ast/TestProg8Parser.kt b/compiler/test/ast/TestProg8Parser.kt index 646feb189..780c1ad76 100644 --- a/compiler/test/ast/TestProg8Parser.kt +++ b/compiler/test/ast/TestProg8Parser.kt @@ -852,20 +852,21 @@ class TestProg8Parser: FunSpec( { main { %option force_output sub start() { - ubyte @zp @shared var1 + ubyte @zp @shared @requirezp var1 ubyte @shared @zp var2 ubyte @zp var3 ubyte @shared var4 - ubyte var5 + ubyte @requirezp var5 + ubyte var6 } } """ val result = compileText(C64Target, false, text, writeAssembly = false).assertSuccess() val stmt = result.program.entrypoint.statements - stmt.size shouldBe 10 + stmt.size shouldBe 12 val var1 = stmt[0] as VarDecl var1.sharedWithAsm shouldBe true - var1.zeropage shouldBe ZeropageWish.PREFER_ZEROPAGE + var1.zeropage shouldBe ZeropageWish.REQUIRE_ZEROPAGE val var2 = stmt[2] as VarDecl var2.sharedWithAsm shouldBe true var2.zeropage shouldBe ZeropageWish.PREFER_ZEROPAGE @@ -877,6 +878,9 @@ class TestProg8Parser: FunSpec( { var4.zeropage shouldBe ZeropageWish.DONTCARE val var5 = stmt[8] as VarDecl var5.sharedWithAsm shouldBe false - var5.zeropage shouldBe ZeropageWish.DONTCARE + var5.zeropage shouldBe ZeropageWish.REQUIRE_ZEROPAGE + val var6 = stmt[10] as VarDecl + var6.sharedWithAsm shouldBe false + var6.zeropage shouldBe ZeropageWish.DONTCARE } }) diff --git a/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt b/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt index 0141a31e2..1be837810 100644 --- a/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt +++ b/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt @@ -50,56 +50,22 @@ private fun Prog8ANTLRParser.Statement_blockContext.toAst(): MutableList ZeropageWish.REQUIRE_ZEROPAGE + options.ZEROPAGE().isNotEmpty() -> ZeropageWish.PREFER_ZEROPAGE + else -> ZeropageWish.DONTCARE + } + val shared = options.SHARED().isNotEmpty() return VarDecl( - VarDeclType.VAR, VarDeclOrigin.USERCODE, + type, VarDeclOrigin.USERCODE, datatype()?.toAst() ?: DataType.UNDEFINED, - if(ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE, + zp, arrayindex()?.toAst(), varname.text, - null, + value, ARRAYSIG() != null || arrayindex() != null, - SHARED().isNotEmpty(), + shared, null, toPosition() ) diff --git a/compilerInterfaces/build.gradle b/compilerInterfaces/build.gradle index 0c14b3b32..9f0cc7e30 100644 --- a/compilerInterfaces/build.gradle +++ b/compilerInterfaces/build.gradle @@ -26,6 +26,7 @@ compileTestKotlin { dependencies { implementation project(':compilerAst') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12" // implementation "org.jetbrains.kotlin:kotlin-reflect" } diff --git a/compilerInterfaces/compilerInterfaces.iml b/compilerInterfaces/compilerInterfaces.iml index a78365fea..47ad4485c 100644 --- a/compilerInterfaces/compilerInterfaces.iml +++ b/compilerInterfaces/compilerInterfaces.iml @@ -11,5 +11,6 @@ + \ No newline at end of file diff --git a/compilerInterfaces/src/prog8/compilerinterface/CallGraph.kt b/compilerInterfaces/src/prog8/compilerinterface/CallGraph.kt index 2bd3a2bbc..707d98ec5 100644 --- a/compilerInterfaces/src/prog8/compilerinterface/CallGraph.kt +++ b/compilerInterfaces/src/prog8/compilerinterface/CallGraph.kt @@ -23,6 +23,8 @@ class CallGraph(private val program: Program) : IAstVisitor { visit(program) } + val allIdentifiers: Map, Statement> = allIdentifiersAndTargets + private val usedSubroutines: Set by lazy { calledBy.keys + program.entrypoint } diff --git a/compilerInterfaces/src/prog8/compilerinterface/IMachineDefinition.kt b/compilerInterfaces/src/prog8/compilerinterface/IMachineDefinition.kt index ed57d0e90..294a3437e 100644 --- a/compilerInterfaces/src/prog8/compilerinterface/IMachineDefinition.kt +++ b/compilerInterfaces/src/prog8/compilerinterface/IMachineDefinition.kt @@ -33,5 +33,4 @@ interface IMachineDefinition { fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) fun isIOAddress(address: UInt): Boolean - fun getPreallocatedZeropageVars(): Map> } diff --git a/compilerInterfaces/src/prog8/compilerinterface/Zeropage.kt b/compilerInterfaces/src/prog8/compilerinterface/Zeropage.kt index 15da3f92e..dce73479e 100644 --- a/compilerInterfaces/src/prog8/compilerinterface/Zeropage.kt +++ b/compilerInterfaces/src/prog8/compilerinterface/Zeropage.kt @@ -1,5 +1,8 @@ package prog8.compilerinterface +import com.github.michaelbull.result.Err +import com.github.michaelbull.result.Ok +import com.github.michaelbull.result.Result import prog8.ast.base.* @@ -17,8 +20,6 @@ abstract class Zeropage(protected val options: CompilationOptions) { private val allocations = mutableMapOf>() val free = mutableListOf() // subclasses must set this to the appropriate free locations. - val allowedDatatypes = NumericDatatypes - fun removeReservedFromFreePool() { for (reserved in options.zpReserved) reserve(reserved) @@ -35,7 +36,7 @@ abstract class Zeropage(protected val options: CompilationOptions) { return free.windowed(2).any { it[0] == it[1] - 1u } } - fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: IErrorReporter): UInt { + fun allocate(scopedname: String, datatype: DataType, arraySize: Int?, position: Position?, errors: IErrorReporter): Result, ZeropageDepletedError> { require(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"} if(options.zeropage== ZeropageType.DONTUSE) @@ -43,49 +44,65 @@ abstract class Zeropage(protected val options: CompilationOptions) { val size: Int = when (datatype) { - in ByteDatatypes -> 1 - in WordDatatypes -> 2 + in IntegerDatatypes -> options.compTarget.memorySize(datatype) + DataType.STR, in ArrayDatatypes -> { + val memsize = arraySize!! * options.compTarget.memorySize(ArrayToElementTypes.getValue(datatype)) + if(position!=null) + errors.warn("allocating a large value in zeropage; str/array $memsize bytes", position) + else + errors.warn("$scopedname: allocating a large value in zeropage; str/array $memsize bytes", Position.DUMMY) + memsize + } DataType.FLOAT -> { if (options.floats) { + val memsize = options.compTarget.memorySize(DataType.FLOAT) if(position!=null) - errors.warn("allocated a large value (float) in zeropage", position) + errors.warn("allocating a large value in zeropage; float $memsize bytes", position) else - errors.warn("$scopedname: allocated a large value (float) in zeropage", Position.DUMMY) - options.compTarget.machine.FLOAT_MEM_SIZE + errors.warn("$scopedname: allocating a large value in zeropage; float $memsize bytes", Position.DUMMY) + memsize } else throw InternalCompilerException("floating point option not enabled") } else -> throw InternalCompilerException("cannot put datatype $datatype in zeropage") } - if(free.size > 0) { - if(size==1) { - for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) { - if(loneByte(candidate)) - return makeAllocation(candidate, 1, datatype, scopedname) + synchronized(this) { + if(free.size > 0) { + if(size==1) { + for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) { + if(oneSeparateByteFree(candidate)) + return Ok(Pair(makeAllocation(candidate, 1, datatype, scopedname), 1)) + } + return Ok(Pair(makeAllocation(free[0], 1, datatype, scopedname), 1)) + } + for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) { + if (sequentialFree(candidate, size)) + return Ok(Pair(makeAllocation(candidate, size, datatype, scopedname), size)) } - return makeAllocation(free[0], 1, datatype, scopedname) - } - for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) { - if (sequentialFree(candidate, size)) - return makeAllocation(candidate, size, datatype, scopedname) } } - throw ZeropageDepletedError("ERROR: no free space in ZP to allocate $size sequential bytes") + return Err(ZeropageDepletedError("no more free space in ZP to allocate $size sequential bytes")) } - protected fun reserve(range: UIntRange) = free.removeAll(range) + private fun reserve(range: UIntRange) = free.removeAll(range) private fun makeAllocation(address: UInt, size: Int, datatype: DataType, name: String?): UInt { require(size>=0) free.removeAll(address until address+size.toUInt()) allocations[address] = (name ?: "") to datatype + if(name!=null) + allocatedVariables[name] = (address to size) to datatype return address } - private fun loneByte(address: UInt) = address in free && address-1u !in free && address+1u !in free + private fun oneSeparateByteFree(address: UInt) = address in free && address-1u !in free && address+1u !in free private fun sequentialFree(address: UInt, size: Int): Boolean { require(size>0) return free.containsAll((address until address+size.toUInt()).toList()) } + + fun allocatedZeropageVariable(scopedname: String): Pair, DataType>? = allocatedVariables[scopedname] + + private val allocatedVariables = mutableMapOf, DataType>>() } diff --git a/docs/source/programming.rst b/docs/source/programming.rst index 481211709..61d79f7f8 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -201,8 +201,8 @@ Values will usually be part of an expression or assignment statement:: *zeropage tag:* If you add the ``@zp`` tag to the variable declaration, the compiler will prioritize this variable -when selecting variables to put into zero page. If there are enough free locations in the zeropage, -it will then try to fill it with as much other variables as possible (before they will be put in regular memory pages). +when selecting variables to put into zero page (but no guarantees). If there are enough free locations in the zeropage, +it will try to fill it with as much other variables as possible (before they will be put in regular memory pages). Example:: byte @zp zeropageCounter = 42 diff --git a/docs/source/syntaxreference.rst b/docs/source/syntaxreference.rst index 45b5b18d7..95a8e15f9 100644 --- a/docs/source/syntaxreference.rst +++ b/docs/source/syntaxreference.rst @@ -266,7 +266,8 @@ Variables should be declared with their exact type and size so the compiler can 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 intial value yourself, zero will be used. You can add a ``@zp`` zeropage-tag, to tell the compiler to prioritize it -when selecting variables to be put into zeropage. +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. You can 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. The syntax is:: diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 679c05fcc..6f3a4df08 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -3,7 +3,11 @@ TODO For next compiler release (7.7) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -... +- fix array and string initialization in zeropage +- fix cx16 zeropage preallocated vars (virtual regs) +- fix ForloopAsmGen zp allocation handling +- document check: arrays and strings can also be placed in zeropage (but almost never should, due to size!) +- document @requirezp and add to syntax def files and IDEA Need help with diff --git a/examples/test.p8 b/examples/test.p8 index b5fa41f73..47668f112 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,38 +1,47 @@ %import textio %import floats %import test_stack -%zeropage basicsafe +%zeropage floatsafe main { %option force_output sub start() { - uword b1 - b1 = 'a' - b1 = b1=='z' ; TODO fix code generation! - txt.print("should print 0: ") - txt.print_ub(b1) - txt.nl() - b1 = 'a' - b1 = b1!='z' ; TODO fix code generation! - txt.print("should print 1: ") - txt.print_ub(b1) - txt.nl() -; str text = "???????????" -; void txt.input_chars(text) -; txt.print("\ninput=") -; txt.print(text) -; txt.nl() -; b1 = text[0]=='z' -; txt.print_ub(b1) -; txt.nl() -; -; if text[0]=='z' { -; txt.print("z!\n") -; } -; -; test(text[0]=='z') + ubyte @requirezp foobar = 2 + uword @requirezp foobar2 = 2 + uword @requirezp foobar3 = 2 + uword @requirezp foobar4 = 2 + uword @requirezp foobar5 = 2 + uword @requirezp foobar6 = 2 + uword @requirezp foobar7 = 2 + uword @requirezp foobar8 = 2 + uword @requirezp foobar9 = 2 + uword @requirezp foobar10 = 2 + uword @requirezp foobar11 = 2 + uword @requirezp foobar12 = 2 + uword @requirezp foobar13 = 2 + uword @requirezp foobar14 = 2 + uword @requirezp foobar15 = 2 + float @shared @requirezp myfloat=1.23456789 + str @shared @requirezp name = "irmen" + ubyte[] @shared @requirezp array = [1,2,3] + + txt.print(name) + txt.nl() + txt.print_ub(array[0]) + txt.spc() + txt.print_ub(array[1]) + txt.spc() + txt.print_ub(array[2]) + txt.nl() + txt.print_uwhex(&name, true) + txt.nl() + txt.print_uwhex(&array, true) + txt.nl() + txt.print_ub(foobar) + txt.nl() + floats.print_f(myfloat) ; float fl ; test_stack.test() @@ -67,12 +76,9 @@ main { ; txt.print_uw(uw) ; txt.nl() ; test_stack.test() - } - sub test(ubyte what) { - txt.print("test: what=") - txt.print_ub(what) - txt.nl() + repeat { + } } sub func() -> ubyte { diff --git a/parser/antlr/Prog8ANTLR.g4 b/parser/antlr/Prog8ANTLR.g4 index 9107c0548..016d3f263 100644 --- a/parser/antlr/Prog8ANTLR.g4 +++ b/parser/antlr/Prog8ANTLR.g4 @@ -45,13 +45,11 @@ SINGLECHAR : '\'' ( STRING_ESCAPE_SEQ | ~[\\\r\n\f'] ) '\'' ; -ZEROPAGE : - '@zp' - ; +ZEROPAGE : '@zp' ; -SHARED : - '@shared' - ; +ZEROPAGEREQUIRE : '@requirezp' ; + +SHARED : '@shared' ; ARRAYSIG : '[]' @@ -133,7 +131,9 @@ directive : directivearg : stringliteral | identifier | integerliteral ; -vardecl: datatype (arrayindex | ARRAYSIG)? SHARED? ZEROPAGE? SHARED? varname=identifier ; +vardecl: datatype (arrayindex | ARRAYSIG)? decloptions varname=identifier ; + +decloptions: (SHARED | ZEROPAGE | ZEROPAGEREQUIRE)* ; varinitializer : vardecl '=' expression ;