add @requirezp and allow str/array to be on zp (with warning)

This commit is contained in:
Irmen de Jong 2022-01-14 23:16:05 +01:00
parent 8e56656c8d
commit 641477d6f6
19 changed files with 310 additions and 242 deletions

View File

@ -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<String, Pair<UInt, DataType>> = emptyMap()
override fun initializeZeropage(compilerOptions: CompilationOptions) {
zeropage = C128Zeropage(compilerOptions)

View File

@ -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<String, Pair<UInt, DataType>> = emptyMap()
override fun initializeZeropage(compilerOptions: CompilationOptions) {
zeropage = C64Zeropage(compilerOptions)

View File

@ -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<String>()
private val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
private val allocatedZeropageVariables = compTarget.machine.getPreallocatedZeropageVars().toMutableMap()
private val breakpointLabels = mutableListOf<String>()
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, "<error>", outputDir, compTarget.name)
}
}
private fun allocateAllZeropageVariables() {
if(options.zeropage==ZeropageType.DONTUSE)
return
val allVariables = this.callGraph.allIdentifiers.asSequence()
.map { it.value }
.filterIsInstance<VarDecl>()
.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<Statement>, inBlock: Block?) {
out("\n; non-zeropage variables")
val vars = statements.asSequence().filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
val vars = statements.asSequence()
.filterIsInstance<VarDecl>()
.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) {

View File

@ -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""")

View File

@ -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<String, Pair<UInt, DataType>> {
val vars = mutableMapOf<String, Pair<UInt, DataType>>()
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<String, Pair<UInt, DataType>> {
// val vars = mutableMapOf<String, Pair<UInt, DataType>>()
// 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)

View File

@ -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")

View File

@ -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<IllegalArgumentException> {
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<InternalCompilerException> {
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<InternalCompilerException> {
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<InternalCompilerException> {
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<ZeropageDepletedError> {
// 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<ZeropageDepletedError> {
zp.allocate("", DataType.UBYTE, null, errors)
zp.allocate("", DataType.UBYTE, null, null, errors)
}
shouldThrow<ZeropageDepletedError> {
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<ZeropageDepletedError> {
// 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<ZeropageDepletedError> {
// 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
}

View File

@ -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
}
})

View File

@ -50,56 +50,22 @@ private fun Prog8ANTLRParser.Statement_blockContext.toAst(): MutableList<Stateme
statement().asSequence().map { it.toAst() }.toMutableList()
private fun Prog8ANTLRParser.VariabledeclarationContext.toAst() : Statement {
vardecl()?.let { return it.toAst() }
vardecl()?.let {
return it.toAst(VarDeclType.VAR, null)
}
varinitializer()?.let {
val vd = it.vardecl()
return VarDecl(
VarDeclType.VAR, VarDeclOrigin.USERCODE,
vd.datatype()?.toAst() ?: DataType.UNDEFINED,
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(),
vd.varname.text,
it.expression().toAst(),
vd.ARRAYSIG() != null || vd.arrayindex() != null,
vd.SHARED().isNotEmpty(),
null,
it.toPosition()
)
return it.vardecl().toAst(VarDeclType.VAR, it.expression().toAst())
}
constdecl()?.let {
val cvarinit = it.varinitializer()
val vd = cvarinit.vardecl()
return VarDecl(
VarDeclType.CONST, VarDeclOrigin.USERCODE,
vd.datatype()?.toAst() ?: DataType.UNDEFINED,
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(),
vd.varname.text,
cvarinit.expression().toAst(),
vd.ARRAYSIG() != null || vd.arrayindex() != null,
vd.SHARED().isNotEmpty(),
null,
cvarinit.toPosition()
)
return cvarinit.vardecl().toAst(VarDeclType.CONST, cvarinit.expression().toAst())
}
memoryvardecl()?.let {
val mvarinit = it.varinitializer()
val vd = mvarinit.vardecl()
return VarDecl(
VarDeclType.MEMORY, VarDeclOrigin.USERCODE,
vd.datatype()?.toAst() ?: DataType.UNDEFINED,
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(),
vd.varname.text,
mvarinit.expression().toAst(),
vd.ARRAYSIG() != null || vd.arrayindex() != null,
vd.SHARED().isNotEmpty(),
null,
mvarinit.toPosition()
)
return mvarinit.vardecl().toAst(VarDeclType.MEMORY, mvarinit.expression().toAst())
}
throw FatalAstException("weird variable decl $this")
@ -598,16 +564,23 @@ private fun Prog8ANTLRParser.When_choiceContext.toAst(): WhenChoice {
return WhenChoice(values?.toMutableList(), scope, toPosition())
}
private fun Prog8ANTLRParser.VardeclContext.toAst(): VarDecl {
private fun Prog8ANTLRParser.VardeclContext.toAst(type: VarDeclType, value: Expression?): VarDecl {
val options = decloptions()
val zp = when {
options.ZEROPAGEREQUIRE().isNotEmpty() -> 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()
)

View File

@ -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"
}

View File

@ -11,5 +11,6 @@
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="compilerAst" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
</component>
</module>

View File

@ -23,6 +23,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
visit(program)
}
val allIdentifiers: Map<Pair<IdentifierReference, Position>, Statement> = allIdentifiersAndTargets
private val usedSubroutines: Set<Subroutine> by lazy {
calledBy.keys + program.entrypoint
}

View File

@ -33,5 +33,4 @@ interface IMachineDefinition {
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path)
fun isIOAddress(address: UInt): Boolean
fun getPreallocatedZeropageVars(): Map<String, Pair<UInt, DataType>>
}

View File

@ -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<UInt, Pair<String, DataType>>()
val free = mutableListOf<UInt>() // 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<Pair<UInt, Int>, 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 ?: "<unnamed>") 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<Pair<UInt, Int>, DataType>? = allocatedVariables[scopedname]
private val allocatedVariables = mutableMapOf<String, Pair<Pair<UInt, Int>, DataType>>()
}

View File

@ -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

View File

@ -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::

View File

@ -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

View File

@ -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 {

View File

@ -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 ;