From 8c2e6971fc516e0f8a1924e593cf74148026d5b6 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Tue, 8 Feb 2022 19:53:47 +0100 Subject: [PATCH] start using vars instead of callgraph (2) --- .../src/prog8/codegen/cpu6502/AsmGen.kt | 2 +- .../prog8/codegen/cpu6502/ForLoopsAsmGen.kt | 4 +- .../src/prog8/codegen/cpu6502/ProgramGen.kt | 146 +++++++++--------- .../codegen/cpu6502/VariableAllocation.kt | 15 +- .../prog8/codegen/target/cx16/CX16Zeropage.kt | 12 +- .../src/prog8/optimizer/UnusedCodeRemover.kt | 3 +- compiler/res/prog8lib/conv.p8 | 2 +- compiler/test/ZeropageTests.kt | 68 ++++---- compilerAst/src/prog8/ast/base/Base.kt | 10 ++ .../src/prog8/ast/statements/AstStatements.kt | 15 -- .../src/prog8/compilerinterface/Zeropage.kt | 38 +++-- docs/source/todo.rst | 1 + examples/test.p8 | 68 ++++---- 13 files changed, 194 insertions(+), 190 deletions(-) diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt index 084794d94..6bcc94c8b 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt @@ -777,7 +777,7 @@ $repeatLabel lda $counterVar val counterVar = makeLabel("counter") when(dt) { DataType.UBYTE, DataType.UWORD -> { - val result = zeropage.allocate(listOf(counterVar), dt, null, stmt.position, errors) + val result = zeropage.allocate(listOf(counterVar), dt, null, null, stmt.position, errors) result.fold( success = { (address, _) -> asmInfo.extraVars.add(Triple(dt, counterVar, address)) }, failure = { asmInfo.extraVars.add(Triple(dt, counterVar, null)) } // allocate normally diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/ForLoopsAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/ForLoopsAsmGen.kt index 2dcff3e45..14a1a2b0a 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/ForLoopsAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/ForLoopsAsmGen.kt @@ -291,7 +291,7 @@ $loopLabel sty $indexVar } if(length>=16) { // allocate index var on ZP if possible - val result = asmgen.zeropage.allocate(listOf(indexVar), DataType.UBYTE, null, stmt.position, asmgen.errors) + val result = asmgen.zeropage.allocate(listOf(indexVar), DataType.UBYTE, null, null, stmt.position, asmgen.errors) result.fold( success = { (address,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") }, failure = { asmgen.out("$indexVar .byte 0") } @@ -332,7 +332,7 @@ $loopLabel sty $indexVar } if(length>=16) { // allocate index var on ZP if possible - val result = asmgen.zeropage.allocate(listOf(indexVar), DataType.UBYTE, null, stmt.position, asmgen.errors) + val result = asmgen.zeropage.allocate(listOf(indexVar), DataType.UBYTE, null, null, stmt.position, asmgen.errors) result.fold( success = { (address,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") }, failure = { asmgen.out("$indexVar .byte 0") } diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramGen.kt index 7512201f2..e08b5daed 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/ProgramGen.kt @@ -32,8 +32,6 @@ internal class ProgramGen( private val blockVariableInitializers = program.allBlocks.associateWith { it.statements.filterIsInstance() } internal fun generate() { - // variables.dump(program.memsizer) // TODO - val allInitializers = blockVariableInitializers.asSequence().flatMap { it.value } require(allInitializers.all { it.origin==AssignmentOrigin.VARINIT }) {"all block-level assignments must be a variable initializer"} @@ -304,30 +302,31 @@ internal class ProgramGen( } // string and array variables in zeropage that have initializer value, should be initialized - val stringVarsInZp = allocation.varsInZeropage.filter { it.datatype==DataType.STR && it.value!=null } - val arrayVarsInZp = allocation.varsInZeropage.filter { it.datatype in ArrayDatatypes && it.value!=null } - if(stringVarsInZp.isNotEmpty() || arrayVarsInZp.isNotEmpty()) { + val stringVarsWithInitInZp = asmgen.zeropage.variables.filter { it.value.dt==DataType.STR && it.value.initialStringValue!=null } + val arrayVarsWithInitInZp = asmgen.zeropage.variables.filter { it.value.dt in ArrayDatatypes && it.value.initialArrayValue!=null } + if(stringVarsWithInitInZp.isNotEmpty() || arrayVarsWithInitInZp.isNotEmpty()) { asmgen.out("; zp str and array initializations") - stringVarsInZp.forEach { + stringVarsWithInitInZp.forEach { + val name = asmgen.asmVariableName(it.key) asmgen.out(""" - lda #<${it.name} - ldy #>${it.name} + lda #<${name} + ldy #>${name} sta P8ZP_SCRATCH_W1 sty P8ZP_SCRATCH_W1+1 - lda #<${it.name}_init_value - ldy #>${it.name}_init_value + lda #<${name}_init_value + ldy #>${name}_init_value jsr prog8_lib.strcpy""") } - arrayVarsInZp.forEach { - val numelements = (it.value as ArrayLiteralValue).value.size - val size = numelements * program.memsizer.memorySize(ArrayToElementTypes.getValue(it.datatype)) + arrayVarsWithInitInZp.forEach { + val size = it.value.size + val name = asmgen.asmVariableName(it.key) asmgen.out(""" - lda #<${it.name}_init_value - ldy #>${it.name}_init_value + lda #<${name}_init_value + ldy #>${name}_init_value sta cx16.r0L sty cx16.r0H - lda #<${it.name} - ldy #>${it.name} + lda #<${name} + ldy #>${name} sta cx16.r1L sty cx16.r1H lda #<$size @@ -337,11 +336,16 @@ internal class ProgramGen( asmgen.out(" jmp +") } - stringVarsInZp.forEach { - outputStringvar(it, it.name+"_init_value") + stringVarsWithInitInZp.forEach { + val varname = asmgen.asmVariableName(it.key)+"_init_value" + val sv = it.value.initialStringValue!! + outputStringvar(varname, it.value.dt, sv.encoding, sv.value) } - arrayVarsInZp.forEach { - vardecl2asm(it, it.name+"_init_value") + + arrayVarsWithInitInZp.forEach { + val varname = asmgen.asmVariableName(it.key)+"_init_value" + val av = it.value.initialArrayValue!! + arrayVardecl2asm(varname, it.value.dt, av, null) } asmgen.out("""+ tsx @@ -365,7 +369,7 @@ internal class ProgramGen( if(variable.zeropage != ZeropageWish.NOT_IN_ZEROPAGE && variable.datatype in IntegerDatatypes && options.zeropage != ZeropageType.DONTUSE) { - val result = asmgen.zeropage.allocate(scopedName, variable.datatype, null, null, errors) + val result = asmgen.zeropage.allocate(scopedName, variable.datatype, null, null, null, errors) errors.report() result.fold( success = { (address, _) -> asmgen.out("${variable.name} = $address\t; zp ${variable.datatype}") }, @@ -374,13 +378,13 @@ internal class ProgramGen( } } else { // Var has been placed in ZP, just output the address - val lenspec = when(zpAlloc.second.first) { + val lenspec = when(zpAlloc.dt) { DataType.FLOAT, DataType.STR, - in ArrayDatatypes -> " ${zpAlloc.second.second} bytes" + in ArrayDatatypes -> " ${zpAlloc.size} bytes" else -> "" } - asmgen.out("${variable.name} = ${zpAlloc.first}\t; zp ${variable.datatype} $lenspec") + asmgen.out("${variable.name} = ${zpAlloc.address}\t; zp ${variable.datatype} $lenspec") } } } @@ -418,66 +422,67 @@ internal class ProgramGen( DataType.STR -> { throw AssemblyError("all string vars should have been interned into prog") } + in ArrayDatatypes -> arrayVardecl2asm(name, decl.datatype, decl.value as? ArrayLiteralValue, decl.arraysize?.constIndex()) + else -> { + throw AssemblyError("weird dt") + } + } + } + + private fun arrayVardecl2asm(varname: String, dt: DataType, value: ArrayLiteralValue?, orNumberOfZeros: Int?) { + when(dt) { DataType.ARRAY_UB -> { - val data = makeArrayFillDataUnsigned(decl) + val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros) if (data.size <= 16) - asmgen.out("$name\t.byte ${data.joinToString()}") + asmgen.out("$varname\t.byte ${data.joinToString()}") else { - asmgen.out(name) + asmgen.out(varname) for (chunk in data.chunked(16)) asmgen.out(" .byte " + chunk.joinToString()) } } DataType.ARRAY_B -> { - val data = makeArrayFillDataSigned(decl) + val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros) if (data.size <= 16) - asmgen.out("$name\t.char ${data.joinToString()}") + asmgen.out("$varname\t.char ${data.joinToString()}") else { - asmgen.out(name) + asmgen.out(varname) for (chunk in data.chunked(16)) asmgen.out(" .char " + chunk.joinToString()) } } DataType.ARRAY_UW -> { - val data = makeArrayFillDataUnsigned(decl) + val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros) if (data.size <= 16) - asmgen.out("$name\t.word ${data.joinToString()}") + asmgen.out("$varname\t.word ${data.joinToString()}") else { - asmgen.out(name) + asmgen.out(varname) for (chunk in data.chunked(16)) asmgen.out(" .word " + chunk.joinToString()) } } DataType.ARRAY_W -> { - val data = makeArrayFillDataSigned(decl) + val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros) if (data.size <= 16) - asmgen.out("$name\t.sint ${data.joinToString()}") + asmgen.out("$varname\t.sint ${data.joinToString()}") else { - asmgen.out(name) + asmgen.out(varname) for (chunk in data.chunked(16)) asmgen.out(" .sint " + chunk.joinToString()) } } DataType.ARRAY_F -> { - val array = - if(decl.value!=null) - (decl.value as ArrayLiteralValue).value - else { - // no init value, use zeros - val zero = decl.zeroElementValue() - Array(decl.arraysize!!.constIndex()!!) { zero } - } + val array = value?.value ?: + Array(orNumberOfZeros!!) { defaultZero(ArrayToElementTypes.getValue(dt), Position.DUMMY) } val floatFills = array.map { val number = (it as NumericLiteralValue).number compTarget.machine.getFloat(number).makeFloatFillAsm() } - asmgen.out(name) + asmgen.out(varname) for (f in array.zip(floatFills)) asmgen.out(" .byte ${f.second} ; float ${f.first}") } - else -> { - throw AssemblyError("weird dt") - } + else -> throw AssemblyError("require array dt") } } @@ -543,23 +548,21 @@ internal class ProgramGen( private fun outputStringvar(strdecl: VarDecl, nameOverride: String?=null) { val varname = nameOverride ?: strdecl.name val sv = strdecl.value as StringLiteralValue - asmgen.out("$varname\t; ${strdecl.datatype} ${sv.encoding}:\"${escape(sv.value).replace("\u0000", "")}\"") - val bytes = compTarget.encodeString(sv.value, sv.encoding).plus(0.toUByte()) + outputStringvar(varname, strdecl.datatype, sv.encoding, sv.value) + } + + private fun outputStringvar(varname: String, dt: DataType, encoding: Encoding, value: String) { + asmgen.out("$varname\t; $dt $encoding:\"${escape(value).replace("\u0000", "")}\"") + val bytes = compTarget.encodeString(value, encoding).plus(0.toUByte()) val outputBytes = bytes.map { "$" + it.toString(16).padStart(2, '0') } for (chunk in outputBytes.chunked(16)) asmgen.out(" .byte " + chunk.joinToString()) } - private fun makeArrayFillDataUnsigned(decl: VarDecl): List { - val array = - if(decl.value!=null) - (decl.value as ArrayLiteralValue).value - else { - // no array init value specified, use a list of zeros - val zero = decl.zeroElementValue() - Array(decl.arraysize!!.constIndex()!!) { zero } - } - return when (decl.datatype) { + private fun makeArrayFillDataUnsigned(dt: DataType, value: ArrayLiteralValue?, orNumberOfZeros: Int?): List { + val array = value?.value ?: + Array(orNumberOfZeros!!) { defaultZero(ArrayToElementTypes.getValue(dt), Position.DUMMY) } + return when (dt) { DataType.ARRAY_UB -> // byte array can never contain pointer-to types, so treat values as all integers array.map { @@ -580,23 +583,14 @@ internal class ProgramGen( else -> throw AssemblyError("weird array elt dt") } } - else -> throw AssemblyError("invalid arraysize type") + else -> throw AssemblyError("invalid dt") } } - private fun makeArrayFillDataSigned(decl: VarDecl): List { - val array = - if(decl.value!=null) { - if(decl.value !is ArrayLiteralValue) - throw AssemblyError("can only use array literal values as array initializer value") - (decl.value as ArrayLiteralValue).value - } - else { - // no array init value specified, use a list of zeros - val zero = decl.zeroElementValue() - Array(decl.arraysize!!.constIndex()!!) { zero } - } - return when (decl.datatype) { + private fun makeArrayFillDataSigned(dt: DataType, value: ArrayLiteralValue?, orNumberOfZeros: Int?): List { + val array = value?.value ?: + Array(orNumberOfZeros!!) { defaultZero(ArrayToElementTypes.getValue(dt), Position.DUMMY) } + return when (dt) { DataType.ARRAY_UB -> // byte array can never contain pointer-to types, so treat values as all integers array.map { @@ -625,7 +619,7 @@ internal class ProgramGen( else "-$$hexnum" } - else -> throw AssemblyError("invalid arraysize type ${decl.datatype}") + else -> throw AssemblyError("invalid dt") } } diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/VariableAllocation.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/VariableAllocation.kt index bc0db293a..6830de481 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/VariableAllocation.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/VariableAllocation.kt @@ -1,11 +1,9 @@ package prog8.codegen.cpu6502 -import com.github.michaelbull.result.fold -import com.github.michaelbull.result.onSuccess +import com.github.michaelbull.result.onFailure import prog8.ast.base.ArrayDatatypes import prog8.ast.base.DataType import prog8.ast.expressions.StringLiteralValue -import prog8.ast.statements.VarDecl import prog8.ast.statements.ZeropageWish import prog8.compilerinterface.CompilationOptions import prog8.compilerinterface.IErrorReporter @@ -14,7 +12,6 @@ import prog8.compilerinterface.ZeropageType internal class VariableAllocation(val vars: IVariablesAndConsts, val errors: IErrorReporter) { - val varsInZeropage = mutableSetOf() fun allocateAllZeropageVariables(options: CompilationOptions) { if(options.zeropage== ZeropageType.DONTUSE) @@ -75,11 +72,8 @@ internal class VariableAllocation(val vars: IVariablesAndConsts, val errors: IEr } else -> null } - val result = zeropage.allocate(scopedname, vardecl.datatype, numElements, vardecl.position, errors) - result.fold( - success = { varsInZeropage.add(vardecl) }, - failure = { errors.err(it.message!!, vardecl.position) } - ) + val result = zeropage.allocate(scopedname, vardecl.datatype, numElements, vardecl.value, vardecl.position, errors) + result.onFailure { errors.err(it.message!!, vardecl.position) } } if(errors.noErrors()) { varsPreferringZp.forEach { (vardecl, scopedname) -> @@ -92,8 +86,7 @@ internal class VariableAllocation(val vars: IVariablesAndConsts, val errors: IEr } else -> null } - val result = zeropage.allocate(scopedname, vardecl.datatype, arraySize, vardecl.position, errors) - result.onSuccess { varsInZeropage.add(vardecl) } + zeropage.allocate(scopedname, vardecl.datatype, arraySize, vardecl.value, vardecl.position, errors) // no need to check for error, if there is one, just allocate in normal system ram later. } } diff --git a/codeGenTargets/src/prog8/codegen/target/cx16/CX16Zeropage.kt b/codeGenTargets/src/prog8/codegen/target/cx16/CX16Zeropage.kt index bd624300b..e457cd5e5 100644 --- a/codeGenTargets/src/prog8/codegen/target/cx16/CX16Zeropage.kt +++ b/codeGenTargets/src/prog8/codegen/target/cx16/CX16Zeropage.kt @@ -41,12 +41,12 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) { removeReservedFromFreePool() for(reg in 0..15) { - allocatedVariables[listOf("cx16", "r${reg}")] = (2+reg*2).toUInt() to (DataType.UWORD to 2) // cx16.r0 .. cx16.r15 - allocatedVariables[listOf("cx16", "r${reg}s")] = (2+reg*2).toUInt() to (DataType.WORD to 2) // cx16.r0s .. cx16.r15s - allocatedVariables[listOf("cx16", "r${reg}L")] = (2+reg*2).toUInt() to (DataType.UBYTE to 1) // cx16.r0L .. cx16.r15L - allocatedVariables[listOf("cx16", "r${reg}H")] = (3+reg*2).toUInt() to (DataType.UBYTE to 1) // cx16.r0H .. cx16.r15H - allocatedVariables[listOf("cx16", "r${reg}sL")] = (2+reg*2).toUInt() to (DataType.BYTE to 1) // cx16.r0sL .. cx16.r15sL - allocatedVariables[listOf("cx16", "r${reg}sH")] = (3+reg*2).toUInt() to (DataType.BYTE to 1) // cx16.r0sH .. cx16.r15sH + allocatedVariables[listOf("cx16", "r${reg}")] = ZpAllocation((2+reg*2).toUInt(), DataType.UWORD, 2, null, null) // cx16.r0 .. cx16.r15 + allocatedVariables[listOf("cx16", "r${reg}s")] = ZpAllocation((2+reg*2).toUInt(), DataType.WORD, 2, null, null) // cx16.r0s .. cx16.r15s + allocatedVariables[listOf("cx16", "r${reg}L")] = ZpAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1, null, null) // cx16.r0L .. cx16.r15L + allocatedVariables[listOf("cx16", "r${reg}H")] = ZpAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1, null, null) // cx16.r0H .. cx16.r15H + allocatedVariables[listOf("cx16", "r${reg}sL")] = ZpAllocation((2+reg*2).toUInt(), DataType.BYTE, 1, null, null) // cx16.r0sL .. cx16.r15sL + allocatedVariables[listOf("cx16", "r${reg}sH")] = ZpAllocation((3+reg*2).toUInt(), DataType.BYTE, 1, null, null) // cx16.r0sH .. cx16.r15sH } } } diff --git a/codeOptimizers/src/prog8/optimizer/UnusedCodeRemover.kt b/codeOptimizers/src/prog8/optimizer/UnusedCodeRemover.kt index 964181b34..d51c24c0e 100644 --- a/codeOptimizers/src/prog8/optimizer/UnusedCodeRemover.kt +++ b/codeOptimizers/src/prog8/optimizer/UnusedCodeRemover.kt @@ -3,6 +3,7 @@ package prog8.optimizer import prog8.ast.* import prog8.ast.base.DataType import prog8.ast.base.VarDeclType +import prog8.ast.base.defaultZero import prog8.ast.expressions.* import prog8.ast.statements.* import prog8.ast.walk.AstWalker @@ -222,7 +223,7 @@ class UnusedCodeRemover(private val program: Program, val cvalue1 = assign1.value.constValue(program) if(cvalue1!=null && cvalue1.number==0.0 && assign2.target.isSameAs(assign1.target, program) && assign2.isAugmentable) { val value2 = assign2.value - val zero = VarDecl.defaultZero(value2.inferType(program).getOr(DataType.UNDEFINED), value2.position) + val zero = defaultZero(value2.inferType(program).getOr(DataType.UNDEFINED), value2.position) when(value2) { is BinaryExpression -> substituteZeroInBinexpr(value2, zero, assign1, assign2) is PrefixExpression -> substituteZeroInPrefixexpr(value2, zero, assign1, assign2) diff --git a/compiler/res/prog8lib/conv.p8 b/compiler/res/prog8lib/conv.p8 index c6887b8b8..86a2752b6 100644 --- a/compiler/res/prog8lib/conv.p8 +++ b/compiler/res/prog8lib/conv.p8 @@ -7,7 +7,7 @@ conv { ; ----- number conversions to decimal strings ---- - str @shared string_out = "????????????????" ; result buffer for the string conversion routines + str string_out = "????????????????" ; result buffer for the string conversion routines asmsub str_ub0 (ubyte value @ A) clobbers(A,Y) { ; ---- convert the ubyte in A in decimal string form, with left padding 0s (3 positions total) diff --git a/compiler/test/ZeropageTests.kt b/compiler/test/ZeropageTests.kt index 23283478c..3805a96c7 100644 --- a/compiler/test/ZeropageTests.kt +++ b/compiler/test/ZeropageTests.kt @@ -64,26 +64,26 @@ class TestC64Zeropage: FunSpec({ test("testNames") { val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, c64target)) - var result = zp.allocate(emptyList(), DataType.UBYTE, null, null, errors) + var result = zp.allocate(emptyList(), DataType.UBYTE, null, null, null, errors) result.onFailure { fail(it.toString()) } - result = zp.allocate(emptyList(), DataType.UBYTE, null, null, errors) + result = zp.allocate(emptyList(), DataType.UBYTE, null, null, null, errors) result.onFailure { fail(it.toString()) } - result = zp.allocate(listOf("varname"), DataType.UBYTE, null, null, errors) + result = zp.allocate(listOf("varname"), DataType.UBYTE, null, null, null, errors) result.onFailure { fail(it.toString()) } - shouldThrow { zp.allocate(listOf("varname"), DataType.UBYTE, null, null, errors) } - result = zp.allocate(listOf("varname2"), DataType.UBYTE, null, null, errors) + shouldThrow { zp.allocate(listOf("varname"), DataType.UBYTE, null, null, null, errors) } + result = zp.allocate(listOf("varname2"), DataType.UBYTE, null, null, null, errors) result.onFailure { fail(it.toString()) } } test("testZpFloatEnable") { val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, c64target)) - var result = zp.allocate(emptyList(), DataType.FLOAT, null, null, errors) + var result = zp.allocate(emptyList(), DataType.FLOAT, null, null, null, errors) result.expectError { "should be allocation error due to disabled floats" } val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, c64target)) - result = zp2.allocate(emptyList(), DataType.FLOAT, null, null, errors) + result = zp2.allocate(emptyList(), DataType.FLOAT, null, null, null, errors) result.expectError { "should be allocation error due to disabled ZP use" } val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, c64target)) - zp3.allocate(emptyList(), DataType.FLOAT, null, null, errors) + zp3.allocate(emptyList(), DataType.FLOAT, null, null, null, errors) } test("testZpModesWithFloats") { @@ -105,7 +105,7 @@ class TestC64Zeropage: FunSpec({ val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, c64target)) println(zp.free) zp.availableBytes() shouldBe 0 - val result = zp.allocate(emptyList(), DataType.BYTE, null, null, errors) + val result = zp.allocate(emptyList(), DataType.BYTE, null, null, null, errors) result.expectError { "expected error due to disabled ZP use" } } @@ -118,9 +118,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(listOf("test"), DataType.UBYTE, null, null, errors) + zp4.allocate(listOf("test"), DataType.UBYTE, null, null, null, errors) zp4.availableBytes() shouldBe 238 - zp4.allocate(listOf("test2"), DataType.UBYTE, null, null, errors) + zp4.allocate(listOf("test2"), DataType.UBYTE, null, null, null, errors) zp4.availableBytes() shouldBe 237 } @@ -151,19 +151,19 @@ class TestC64Zeropage: FunSpec({ zp.hasByteAvailable() shouldBe true zp.hasWordAvailable() shouldBe true - var result = zp.allocate(emptyList(), DataType.FLOAT, null, null, errors) + var result = zp.allocate(emptyList(), DataType.FLOAT, null, null, null, errors) result.expectError { "expect allocation error: in regular zp there aren't 5 sequential bytes free" } for (i in 0 until zp.availableBytes()) { - val alloc = zp.allocate(emptyList(), DataType.UBYTE, null, null, errors) + val alloc = zp.allocate(emptyList(), DataType.UBYTE, null, null, null, errors) alloc.getOrElse { throw it } } zp.availableBytes() shouldBe 0 zp.hasByteAvailable() shouldBe false zp.hasWordAvailable() shouldBe false - result = zp.allocate(emptyList(), DataType.UBYTE, null, null, errors) + result = zp.allocate(emptyList(), DataType.UBYTE, null, null, null, errors) result.expectError { "expected allocation error" } - result = zp.allocate(emptyList(), DataType.UWORD, null, null, errors) + result = zp.allocate(emptyList(), DataType.UWORD, null, null, null, errors) result.expectError { "expected allocation error" } } @@ -172,47 +172,47 @@ class TestC64Zeropage: FunSpec({ zp.availableBytes() shouldBe 239 zp.hasByteAvailable() shouldBe true zp.hasWordAvailable() shouldBe true - var result = zp.allocate(emptyList(), DataType.UWORD, null, null, errors) + var result = zp.allocate(emptyList(), DataType.UWORD, null, 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(emptyList(), DataType.UWORD, null, null, errors) + zp.allocate(emptyList(), DataType.UWORD, null, null, null, errors) } zp.availableBytes() shouldBe 5 // can't allocate because no more sequential bytes, only fragmented - result = zp.allocate(emptyList(), DataType.UWORD, null, null, errors) + result = zp.allocate(emptyList(), DataType.UWORD, null, null, null, errors) result.expectError { "should give allocation error" } for(i in 0..4) { - zp.allocate(emptyList(), DataType.UBYTE, null, null, errors) + zp.allocate(emptyList(), DataType.UBYTE, null, null, null, errors) } zp.availableBytes() shouldBe 0 zp.hasByteAvailable() shouldBe false zp.hasWordAvailable() shouldBe false - result = zp.allocate(emptyList(), DataType.UBYTE, null, null, errors) + result = zp.allocate(emptyList(), DataType.UBYTE, null, null, null, errors) result.expectError { "should give allocation error" } } test("testEfficientAllocation") { val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, c64target)) zp.availableBytes() shouldBe 18 - zp.allocate(emptyList(), DataType.WORD, null, null, errors ).getOrElse{throw it}.first shouldBe 0x04u - zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.first shouldBe 0x06u - zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.first shouldBe 0x0au - zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.first shouldBe 0x9bu - zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.first shouldBe 0x9eu - zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.first shouldBe 0xa5u - zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.first shouldBe 0xb0u - zp.allocate(emptyList(), DataType.UWORD, null, null, errors).getOrElse{throw it}.first shouldBe 0xbeu - zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.first shouldBe 0x0eu - zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.first shouldBe 0x92u - zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.first shouldBe 0x96u - zp.allocate(emptyList(), DataType.UBYTE, null, null, errors).getOrElse{throw it}.first shouldBe 0xf9u + zp.allocate(emptyList(), DataType.WORD, null, null, null, errors).getOrElse{throw it}.first shouldBe 0x04u + zp.allocate(emptyList(), DataType.UBYTE, null, null, null, errors).getOrElse{throw it}.first shouldBe 0x06u + zp.allocate(emptyList(), DataType.UBYTE, null, null, null, errors).getOrElse{throw it}.first shouldBe 0x0au + zp.allocate(emptyList(), DataType.UWORD, null, null, null, errors).getOrElse{throw it}.first shouldBe 0x9bu + zp.allocate(emptyList(), DataType.UWORD, null, null, null, errors).getOrElse{throw it}.first shouldBe 0x9eu + zp.allocate(emptyList(), DataType.UWORD, null, null, null, errors).getOrElse{throw it}.first shouldBe 0xa5u + zp.allocate(emptyList(), DataType.UWORD, null, null, null, errors).getOrElse{throw it}.first shouldBe 0xb0u + zp.allocate(emptyList(), DataType.UWORD, null, null, null, errors).getOrElse{throw it}.first shouldBe 0xbeu + zp.allocate(emptyList(), DataType.UBYTE, null, null, null, errors).getOrElse{throw it}.first shouldBe 0x0eu + zp.allocate(emptyList(), DataType.UBYTE, null, null, null, errors).getOrElse{throw it}.first shouldBe 0x92u + zp.allocate(emptyList(), DataType.UBYTE, null, null, null, errors).getOrElse{throw it}.first shouldBe 0x96u + zp.allocate(emptyList(), DataType.UBYTE, null, null, null, errors).getOrElse{throw it}.first shouldBe 0xf9u zp.availableBytes() shouldBe 0 } @@ -243,9 +243,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(listOf("test"), DataType.UBYTE, null, null, errors) + zp3.allocate(listOf("test"), DataType.UBYTE, null, null, null, errors) zp3.availableBytes() shouldBe 215 - zp3.allocate(listOf("test2"), DataType.UBYTE, null, null, errors) + zp3.allocate(listOf("test2"), DataType.UBYTE, null, null, null, errors) zp3.availableBytes() shouldBe 214 } diff --git a/compilerAst/src/prog8/ast/base/Base.kt b/compilerAst/src/prog8/ast/base/Base.kt index 1fa01073e..9ecb0a833 100644 --- a/compilerAst/src/prog8/ast/base/Base.kt +++ b/compilerAst/src/prog8/ast/base/Base.kt @@ -1,6 +1,7 @@ package prog8.ast.base import prog8.ast.Node +import prog8.ast.expressions.NumericLiteralValue import kotlin.io.path.Path import kotlin.io.path.absolute @@ -196,3 +197,12 @@ data class Position(val file: String, val line: Int, val startCol: Int, val endC val DUMMY = Position("", 0, 0, 0) } } + +fun defaultZero(dt: DataType, position: Position) = when(dt) { + DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, 0.0, position) + DataType.BYTE -> NumericLiteralValue(DataType.BYTE, 0.0, position) + DataType.UWORD, DataType.STR -> NumericLiteralValue(DataType.UWORD, 0.0, position) + DataType.WORD -> NumericLiteralValue(DataType.WORD, 0.0, position) + DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, 0.0, position) + else -> throw FatalAstException("can only determine default zero value for a numeric type") +} \ No newline at end of file diff --git a/compilerAst/src/prog8/ast/statements/AstStatements.kt b/compilerAst/src/prog8/ast/statements/AstStatements.kt index 25d8fc131..a1f82a419 100644 --- a/compilerAst/src/prog8/ast/statements/AstStatements.kt +++ b/compilerAst/src/prog8/ast/statements/AstStatements.kt @@ -215,15 +215,6 @@ class VarDecl(val type: VarDeclType, return VarDecl(VarDeclType.VAR, VarDeclOrigin.ARRAYLITERAL, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, array, isArray = true, sharedWithAsm = false, subroutineParameter = null, position = array.position) } - - fun defaultZero(dt: DataType, position: Position) = when(dt) { - DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, 0.0, position) - DataType.BYTE -> NumericLiteralValue(DataType.BYTE, 0.0, position) - DataType.UWORD, DataType.STR -> NumericLiteralValue(DataType.UWORD, 0.0, position) - DataType.WORD -> NumericLiteralValue(DataType.WORD, 0.0, position) - DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, 0.0, position) - else -> throw FatalAstException("can only determine default zero value for a numeric type") - } } val datatypeErrors = mutableListOf() // don't crash at init time, report them in the AstChecker @@ -405,12 +396,6 @@ class Assignment(var target: AssignTarget, var value: Expression, var origin: As return false } - - fun initializerFor(program: Program) = - if(origin==AssignmentOrigin.VARINIT) - target.identifier!!.targetVarDecl(program) - else - null } data class AssignTarget(var identifier: IdentifierReference?, diff --git a/compilerInterfaces/src/prog8/compilerinterface/Zeropage.kt b/compilerInterfaces/src/prog8/compilerinterface/Zeropage.kt index d00d4255e..96b19b1dc 100644 --- a/compilerInterfaces/src/prog8/compilerinterface/Zeropage.kt +++ b/compilerInterfaces/src/prog8/compilerinterface/Zeropage.kt @@ -4,6 +4,9 @@ import com.github.michaelbull.result.Err import com.github.michaelbull.result.Ok import com.github.michaelbull.result.Result import prog8.ast.base.* +import prog8.ast.expressions.ArrayLiteralValue +import prog8.ast.expressions.Expression +import prog8.ast.expressions.StringLiteralValue class ZeropageAllocationError(message: String) : Exception(message) @@ -16,12 +19,17 @@ abstract class Zeropage(protected val options: CompilationOptions) { abstract val SCRATCH_W1 : UInt // temp storage 1 for a word $fb+$fc abstract val SCRATCH_W2 : UInt // temp storage 2 for a word $fb+$fc + data class ZpAllocation(val address: UInt, + val dt: DataType, + val size: Int, + val initialStringValue: StringLiteralValue?, + val initialArrayValue: ArrayLiteralValue?) // the variables allocated into Zeropage. // name (scoped) ==> pair of address to (Datatype + bytesize) - protected val allocatedVariables = mutableMapOf, Pair>>() + protected val allocatedVariables = mutableMapOf, ZpAllocation>() private val allocations = mutableMapOf, DataType>>() - public val variables: Map, Pair>> = allocatedVariables + val variables: Map, ZpAllocation> = allocatedVariables val free = mutableListOf() // subclasses must set this to the appropriate free locations. @@ -43,7 +51,13 @@ abstract class Zeropage(protected val options: CompilationOptions) { return free.windowed(2).any { it[0] == it[1] - 1u } } - fun allocate(name: List, datatype: DataType, numElements: Int?, position: Position?, errors: IErrorReporter): Result, ZeropageAllocationError> { + fun allocate(name: List, + datatype: DataType, + numElements: Int?, + initValue: Expression?, + position: Position?, + errors: IErrorReporter): Result, ZeropageAllocationError> { + require(name.isEmpty() || !allocations.values.any { it.first==name } ) {"name can't be allocated twice"} if(options.zeropage== ZeropageType.DONTUSE) @@ -78,13 +92,13 @@ abstract class Zeropage(protected val options: CompilationOptions) { if(size==1) { for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) { if(oneSeparateByteFree(candidate)) - return Ok(Pair(makeAllocation(candidate, 1, datatype, name), 1)) + return Ok(Pair(makeAllocation(candidate, 1, datatype, name, initValue), 1)) } - return Ok(Pair(makeAllocation(free[0], 1, datatype, name), 1)) + return Ok(Pair(makeAllocation(free[0], 1, datatype, name, initValue), 1)) } for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) { if (sequentialFree(candidate, size)) - return Ok(Pair(makeAllocation(candidate, size, datatype, name), size)) + return Ok(Pair(makeAllocation(candidate, size, datatype, name, initValue), size)) } } } @@ -94,12 +108,18 @@ abstract class Zeropage(protected val options: CompilationOptions) { private fun reserve(range: UIntRange) = free.removeAll(range) - private fun makeAllocation(address: UInt, size: Int, datatype: DataType, name: List): UInt { + private fun makeAllocation(address: UInt, size: Int, datatype: DataType, name: List, initValue: Expression?): UInt { require(size>=0) free.removeAll(address until address+size.toUInt()) allocations[address] = name to datatype - if(name.isNotEmpty()) - allocatedVariables[name] = address to (datatype to size) + if(name.isNotEmpty()) { + allocatedVariables[name] = when(datatype) { + in NumericDatatypes -> ZpAllocation(address, datatype, size, null, null) // numerical variables in zeropage never have an initial value here TODO why not? + DataType.STR -> ZpAllocation(address, datatype, size, initValue as? StringLiteralValue, null) + in ArrayDatatypes -> ZpAllocation(address, datatype, size, null, initValue as? ArrayLiteralValue) + else -> throw AssemblyError("invalid dt") + } + } return address } diff --git a/docs/source/todo.rst b/docs/source/todo.rst index e017f7c49..90d8ac610 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -4,6 +4,7 @@ TODO For next release ^^^^^^^^^^^^^^^^ - (newvaralloc) UnusedCodeRemover after(decl: VarDecl): fix that vars defined in a library can also safely be removed if unused. Currently this breaks programs such as textelite (due to diskio.save().end_address ?) +- remove hacks in VariableAllocation Need help with diff --git a/examples/test.p8 b/examples/test.p8 index 8730daed6..e81492e4f 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -4,41 +4,41 @@ main { ubyte @zp mainglobal1=10 + uword [2] nullwords + ubyte [2] nullbytes + uword [2] valuewords = [1111,22222] + ubyte [2] valuebytes = [111,222] + str name = "irmen" + + uword [2] @requirezp zpnullwords + ubyte [2] @requirezp zpnullbytes + uword [2] @requirezp zpvaluewords = [11111,22222] + ubyte [2] @requirezp zpvaluebytes = [111,222] + str @requirezp zpname = "irmenzp" + sub start() { - c64.SETNAM(1, "$") - txt.print("1") - txt.print("12") - txt.print("test.") - txt.print("test.") -; foobar(2,1,1,1) -; foobar2(2,1,1,1) - } - - sub unusedsubroutine() { - c64.SETNAM(1, "$") ; TODO fix don't remove this interned string because referenced in start() - txt.print("this string should be removed from the pool") - txt.print("this string should be removed from the pool") - txt.print("this string should be removed from the pool") - } - -; asmsub foobar2(ubyte argument @A, uword a2 @R0, uword a3 @R1, uword a4 @R2) -> ubyte @A { -; %asm {{ -; lda #0 -; rts -; }} -; } -; sub foobar(ubyte argument, uword a2, uword a3, uword a4) -> ubyte { -; argument++ -; return lsb(a2) -; } -} - -foobar { - str name = "don't remove this one" - - sub unusedinfoobar() { - name = "should be removed" ; TODO remove from interned strings because subroutine got removed + txt.print_uw(nullwords[1]) + txt.nl() + txt.print_ub(nullbytes[1]) + txt.nl() + txt.print_uw(valuewords[1]) + txt.nl() + txt.print_ub(valuebytes[1]) + txt.nl() txt.print(name) - txt.print("should also be removed2") ; TODO remove from interned strings because subroutine got removed + txt.nl() + txt.nl() + + txt.print_uw(zpnullwords[1]) + txt.nl() + txt.print_ub(zpnullbytes[1]) + txt.nl() + txt.print_uw(zpvaluewords[1]) + txt.nl() + txt.print_ub(zpvaluebytes[1]) + txt.nl() + txt.print(zpname) + txt.nl() + } }