finalized zeropage variable allocation

This commit is contained in:
Irmen de Jong 2019-01-27 22:59:40 +01:00
parent 0219c69446
commit 7459896155
16 changed files with 125 additions and 188 deletions

View File

@ -41,14 +41,14 @@ fun printWarning(msg: String, position: Position, detailInfo: String?=null) {
if(detailInfo==null)
print("\n")
else
println(": $detailInfo")
println(": $detailInfo\n")
print("\u001b[0m") // normal
}
fun printWarning(msg: String) {
print("\u001b[93m") // bright yellow
print("Warning: $msg")
print("\u001b[0m") // normal
print("\u001b[0m\n") // normal
}
private class AstChecker(private val namespace: INameScope,
@ -784,7 +784,7 @@ private class AstChecker(private val namespace: INameScope,
if(target is BuiltinFunctionStatementPlaceholder) {
// it's a call to a builtin function.
val func = BuiltinFunctions[target.name]!!
val func = BuiltinFunctions.getValue(target.name)
if(args.size!=func.parameters.size)
checkResult.add(SyntaxError("invalid number of arguments", position))
else {

View File

@ -32,7 +32,7 @@ private class DirectedGraph<VT> {
println("#vertices: $numVertices")
graph.forEach { from, to ->
println("$from CALLS:")
to.forEach { it -> println(" $it") }
to.forEach { println(" $it") }
}
val cycle = checkForCycle()
if(cycle.isNotEmpty()) {

View File

@ -46,6 +46,8 @@ private class StatementReorderer(private val namespace: INameScope, private val
module.statements.removeAll(directives)
module.statements.addAll(0, directives)
// TODO make sure user-defined blocks come BEFORE library blocks
sortConstantAssignments(module.statements)
}

View File

@ -11,49 +11,40 @@ abstract class Zeropage(private val options: CompilationOptions) {
private val allocations = mutableMapOf<Int, Pair<String, DataType>>()
val free = mutableListOf<Int>() // subclasses must set this to the appropriate free locations.
val allowedDatatypes = NumericDatatypes
fun available() = free.size
fun allocate(name: String, type: DataType) =
allocate(VarDecl(VarDeclType.VAR, type, true, null, name, null, Position("",0,0,0)))
fun allocate(vardecl: VarDecl) : Int {
assert(vardecl.name.isEmpty() || !allocations.values.any { it.first==vardecl.name } ) {"same name can't be allocated twice"}
assert(vardecl.type== VarDeclType.VAR) {"can only allocate VAR type"}
fun allocate(scopedname: String, datatype: DataType, position: Position?): Int {
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"same scopedname can't be allocated twice"}
val size =
if(vardecl.arrayspec!=null) {
printWarning("allocating a large value (arrayspec) in zeropage", vardecl.position)
when(vardecl.datatype) {
DataType.UBYTE, DataType.BYTE -> (vardecl.arrayspec.x as LiteralValue).asIntegerValue!!
DataType.UWORD, DataType.UWORD -> (vardecl.arrayspec.x as LiteralValue).asIntegerValue!! * 2
DataType.FLOAT -> (vardecl.arrayspec.x as LiteralValue).asIntegerValue!! * 5
else -> throw CompilerException("array can only be of byte, word, float")
}
} else {
when (vardecl.datatype) {
when (datatype) {
DataType.UBYTE, DataType.BYTE -> 1
DataType.UWORD, DataType.WORD -> 2
DataType.FLOAT -> {
if (options.floats) {
printWarning("allocating a large value (float) in zeropage", vardecl.position)
if(position!=null)
printWarning("allocated a large value (float) in zeropage", position)
else
printWarning("$scopedname: allocated a large value (float) in zeropage")
5
} else throw CompilerException("floating point option not enabled")
}
else -> throw CompilerException("cannot put datatype ${vardecl.datatype} in zeropage")
else -> throw CompilerException("cannot put datatype $datatype in zeropage")
}
}
if(free.size > 0) {
if(size==1) {
for(candidate in free.min()!! .. free.max()!!+1) {
if(loneByte(candidate))
return makeAllocation(candidate, 1, vardecl.datatype, vardecl.name)
return makeAllocation(candidate, 1, datatype, scopedname)
}
return makeAllocation(free[0], 1, vardecl.datatype, vardecl.name)
return makeAllocation(free[0], 1, datatype, scopedname)
}
for(candidate in free.min()!! .. free.max()!!+1) {
if (sequentialFree(candidate, size))
return makeAllocation(candidate, size, vardecl.datatype, vardecl.name)
return makeAllocation(candidate, size, datatype, scopedname)
}
}

View File

@ -45,10 +45,8 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
if (zpVariables.isNotEmpty()) {
for (variable in zpVariables) {
try {
val address = zeropage.allocate(variable.key, variable.value.type)
val address = zeropage.allocate(variable.key, variable.value.type, null)
allocatedZeropageVariables[variable.key] = Pair(address, variable.value.type)
println("DEBUG: allocated on ZP: $variable @ $address") //TODO
block.variablesMarkedForZeropage.remove(variable.key)//TODO weg
} catch (x: ZeropageDepletedError) {
printWarning(x.toString() + " variable ${variable.key} type ${variable.value.type}")
notAllocated++
@ -56,7 +54,6 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
}
}
}
println("DEBUG: ${allocatedZeropageVariables.size} variables allocated in Zeropage") // TODO
if(notAllocated>0)
printWarning("$notAllocated variables marked for Zeropage could not be allocated there")
}

View File

@ -62,6 +62,10 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
program.blocks.clear()
program.blocks.addAll(newblocks)
val newAllocatedZp = program.allocatedZeropageVariables.map { symname(it.key, null) to it.value}
program.allocatedZeropageVariables.clear()
program.allocatedZeropageVariables.putAll(newAllocatedZp)
// make a list of all const floats that are used
for(block in program.blocks) {
for(ins in block.instructions.filter{it.arg?.type==DataType.FLOAT}) {
@ -103,20 +107,27 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
}
private fun symname(scoped: String, block: IntermediateProgram.ProgramBlock): String {
private fun symname(scoped: String, block: IntermediateProgram.ProgramBlock?): String {
if(' ' in scoped)
return scoped
val blockLocal: Boolean
var name = if (scoped.startsWith("${block.shortname}.")) {
blockLocal = true
scoped.substring(block.shortname.length+1)
} else if (scoped.startsWith("block.")) {
blockLocal = false
scoped
} else {
blockLocal = false
scoped
var name = when {
block==null -> {
blockLocal=true
scoped
}
scoped.startsWith("${block.shortname}.") -> {
blockLocal = true
scoped.substring(block.shortname.length+1)
}
scoped.startsWith("block.") -> {
blockLocal = false
scoped
}
else -> {
blockLocal = false
scoped
}
}
name = name.replace("<<<", "prog8_").replace(">>>", "")
if(name=="-")
@ -203,7 +214,29 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
out("* = ${block.address?.toHex()}")
}
// @TODO allocate variables on the zeropage as long as there is space
// deal with zeropage variables
for(variable in blk.variables) {
val sym = symname(blk.scopedname+"."+variable.key, null)
val zpVar = program.allocatedZeropageVariables[sym]
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)
if(variable.value.type in zeropage.allowedDatatypes && variable.value.type != DataType.FLOAT) {
try {
val address = zeropage.allocate(sym, variable.value.type, null)
out("${variable.key} = $address\t; zp ${variable.value.type}")
// make sure we add the var to the set of zpvars for this block
blk.variablesMarkedForZeropage.add(variable.key)
program.allocatedZeropageVariables[sym] = Pair(address, variable.value.type)
} catch (x: ZeropageDepletedError) {
// leave it as it is.
}
}
}
else {
// it was already allocated on the zp
out("${variable.key} = ${zpVar.first}\t; zp ${zpVar.second}")
}
}
out("\n; memdefs and kernel subroutines")
memdefs2asm(block)
@ -235,7 +268,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
}
private fun vardecls2asm(block: IntermediateProgram.ProgramBlock) {
// these are the non-zeropage variables @todo is this correct now?
// these are the non-zeropage variables
val sortedVars = block.variables.filter{it.key !in block.variablesMarkedForZeropage}.toList().sortedBy { it.second.type }
for (v in sortedVars) {
when (v.second.type) {

View File

@ -25,10 +25,6 @@ const val ESTACK_HI = 0xcf00 // $cf00-$cfff inclusive
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
// @todo: actually USE the zero page when allocating variables at code generation time
// (ideally, the variables that are used 'most' / inside long loops are allocated in ZP first)
companion object {
const val SCRATCH_B1 = 0x02
const val SCRATCH_REG = 0x03 // temp storage for a register

View File

@ -139,7 +139,7 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespa
throw FatalAstException("function requires one argument which is an arrayspec $function")
}
val func = BuiltinFunctions[function]!!
val func = BuiltinFunctions.getValue(function)
if(func.returntype!=null)
return func.returntype
// function has return values, but the return type depends on the arguments

View File

@ -6,7 +6,6 @@ import prog8.compiler.LauncherType
import prog8.compiler.OutputType
import prog8.determineCompilationOptions
import java.io.InputStream
import java.net.URL
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths

View File

@ -117,36 +117,29 @@ class TestCompiler {
}
private val dummypos = Position("test", 0, 0, 0)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestZeropage {
@Test
fun testNames() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false))
zp.allocate("", DataType.UBYTE, null)
zp.allocate("", DataType.UBYTE, null)
zp.allocate("varname", DataType.UBYTE, null)
assertFailsWith<AssertionError> {
zp.allocate(VarDecl(VarDeclType.MEMORY, DataType.UBYTE, false, null, "", null, dummypos))
zp.allocate("varname", DataType.UBYTE, null)
}
zp.allocate(VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, "", null, dummypos))
zp.allocate(VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, "", null, dummypos))
zp.allocate(VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, "varname", null, dummypos))
assertFailsWith<AssertionError> {
zp.allocate(VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, "varname", null, dummypos))
}
zp.allocate(VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, "varname2", null, dummypos))
zp.allocate("varname2", DataType.UBYTE, null)
}
@Test
fun testZpFloatEnable() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
assertFailsWith<CompilerException> {
zp.allocate(VarDecl(VarDeclType.VAR, DataType.FLOAT, false, null, "", null, dummypos))
zp.allocate("", DataType.FLOAT, null)
}
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true))
zp2.allocate(VarDecl(VarDeclType.VAR, DataType.FLOAT, false, null, "", null, dummypos))
zp2.allocate("", DataType.FLOAT, null)
}
@Test
@ -187,22 +180,22 @@ class TestZeropage {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
assertEquals(19, zp.available())
zp.allocate(VarDecl(VarDeclType.VAR, DataType.FLOAT, false, null, "", null, dummypos))
zp.allocate("", DataType.FLOAT, null)
assertFailsWith<ZeropageDepletedError> {
// in regular zp there aren't 5 sequential bytes free after we take the first sequence
zp.allocate(VarDecl(VarDeclType.VAR, DataType.FLOAT, false, null, "", null, dummypos))
zp.allocate("", DataType.FLOAT, null)
}
for (i in 0 until zp.available()) {
val loc = zp.allocate(VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, "", null, dummypos))
val loc = zp.allocate("", DataType.UBYTE, null)
assertTrue(loc > 0)
}
assertEquals(0, zp.available())
assertFailsWith<ZeropageDepletedError> {
zp.allocate(VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, "", null, dummypos))
zp.allocate("", DataType.UBYTE, null)
}
assertFailsWith<ZeropageDepletedError> {
zp.allocate(VarDecl(VarDeclType.VAR, DataType.UWORD, false, null, "", null, dummypos))
zp.allocate("", DataType.UWORD, null)
}
}
@ -210,33 +203,33 @@ class TestZeropage {
fun testFullAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true))
assertEquals(238, zp.available())
val loc = zp.allocate(VarDecl(VarDeclType.VAR, DataType.FLOAT, false, null, "", null, dummypos))
val loc = zp.allocate("", DataType.FLOAT, null)
assertTrue(loc > 3)
assertFalse(loc in zp.free)
val num = zp.available() / 5
val rest = zp.available() % 5
for(i in 0..num-4) {
zp.allocate(VarDecl(VarDeclType.VAR, DataType.FLOAT, false, null, "", null, dummypos))
zp.allocate("", DataType.FLOAT, null)
}
assertEquals(18,zp.available())
assertFailsWith<ZeropageDepletedError> {
// can't allocate because no more sequential bytes, only fragmented
zp.allocate(VarDecl(VarDeclType.VAR, DataType.FLOAT, false, null, "", null, dummypos))
zp.allocate("", DataType.FLOAT, null)
}
for(i in 0..13) {
zp.allocate(VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, "", null, dummypos))
zp.allocate("", DataType.UBYTE, null)
}
zp.allocate(VarDecl(VarDeclType.VAR, DataType.UWORD, false, null, "", null, dummypos))
zp.allocate("", DataType.UWORD, null)
assertEquals(2, zp.available())
zp.allocate(VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, "", null, dummypos))
zp.allocate(VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, "", null, dummypos))
zp.allocate("", DataType.UBYTE, null)
zp.allocate("", DataType.UBYTE, null)
assertFailsWith<ZeropageDepletedError> {
// no more space
zp.allocate(VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, "", null, dummypos))
zp.allocate("", DataType.UBYTE, null)
}
}
@ -244,15 +237,15 @@ class TestZeropage {
fun testEfficientAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
assertEquals(19, zp.available())
assertEquals(0x04, zp.allocate(VarDecl(VarDeclType.VAR, DataType.FLOAT, false, null, "", null, dummypos)))
assertEquals(0x09, zp.allocate(VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, "", null, dummypos)))
assertEquals(0x0d, zp.allocate(VarDecl(VarDeclType.VAR, DataType.UWORD, false, null, "", null, dummypos)))
assertEquals(0x94, zp.allocate(VarDecl(VarDeclType.VAR, DataType.UWORD, false, null, "", null, dummypos)))
assertEquals(0xa7, zp.allocate(VarDecl(VarDeclType.VAR, DataType.UWORD, false, null, "", null, dummypos)))
assertEquals(0xa9, zp.allocate(VarDecl(VarDeclType.VAR, DataType.UWORD, false, null, "", null, dummypos)))
assertEquals(0xb5, zp.allocate(VarDecl(VarDeclType.VAR, DataType.UWORD, false, null, "", null, dummypos)))
assertEquals(0xf7, zp.allocate(VarDecl(VarDeclType.VAR, DataType.UWORD, false, null, "", null, dummypos)))
assertEquals(0xf9, zp.allocate(VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, "", null, dummypos)))
assertEquals(0x04, zp.allocate("", DataType.FLOAT, null))
assertEquals(0x09, zp.allocate("", DataType.UBYTE, null))
assertEquals(0x0d, zp.allocate("", DataType.UWORD, null))
assertEquals(0x94, zp.allocate("", DataType.UWORD, null))
assertEquals(0xa7, zp.allocate("", DataType.UWORD, null))
assertEquals(0xa9, zp.allocate("", DataType.UWORD, null))
assertEquals(0xb5, zp.allocate("", DataType.UWORD, null))
assertEquals(0xf7, zp.allocate("", DataType.UWORD, null))
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null))
assertEquals(0, zp.available())
}
}

View File

@ -7,7 +7,6 @@ import prog8.ast.LiteralValue
import prog8.ast.Position
import prog8.compiler.intermediate.Value
import prog8.compiler.intermediate.ValueException
import prog8.stackvm.VmExecutionException
import kotlin.test.*

View File

@ -34,16 +34,9 @@ Add more compiler optimizations to the existing ones.
Also some library routines and code patterns could perhaps be optimized further
Should use the zeropage for variables
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Variables should be allocated in the zeropage as long as it has space.
- add some sort of ``zp`` modifier keyword on vardecls to force them into zeropage?
Misc
^^^^
- sqrt() should have integer implementation as well, instead of relying on float SQRT for all argument types
- code generation for POW instruction
- make sure user-defined blocks come BEFORE library blocks (this helps zeropage variable allocations)

View File

@ -26,6 +26,7 @@
Y++ ; delay for alignment
Y++ ; delay for alignment
Y++ ; delay for alignment
Y++ ; delay for alignment
ubyte rasterpos = c64.RASTER
if color!=len(colors) {
c64.EXTCOL = colors[color]

View File

@ -56,9 +56,9 @@ sub irq() {
c64.EXTCOL--
; float up & wobble horizontally
for ubyte i in 0 to 14 step 2 {
for ubyte @zp i in 0 to 14 step 2 {
c64.SPXY[i+1]--
ubyte r = rnd()
ubyte @zp r = rnd()
if r>200
c64.SPXY[i]++
else if r<40

View File

@ -1,102 +1,35 @@
%import c64utils
%option enable_floats
~ main {
ubyte b1 = 42
word w = -999
sub start() {
vm_write_num(b1)
vm_write_char('\n')
vm_write_num(w)
vm_write_char('\n')
; c64scr.print_ub(b1)
; c64.CHROUT('\n')
; c64scr.print_w(w)
; c64.CHROUT('\n')
b1=0
w=0
vm_write_num(b1)
vm_write_char('\n')
vm_write_num(w)
vm_write_char('\n')
; c64scr.print_ub(b1)
; c64.CHROUT('\n')
; c64scr.print_w(w)
; c64.CHROUT('\n')
derp.derp()
}
}
ubyte @zp ub = 22
byte @zp b = 22
word @zp w = 2222
uword @zp uw = 2222
~ derp {
ubyte b1 = 55
sub derp() {
word w = -999
vm_write_num(b1)
vm_write_char('\n')
vm_write_num(w)
vm_write_char('\n')
; c64scr.print_ub(b1)
; c64.CHROUT('\n')
; c64scr.print_w(w)
; c64.CHROUT('\n')
b1=0
w=0
vm_write_num(b1)
vm_write_char('\n')
vm_write_num(w)
vm_write_char('\n')
; c64scr.print_ub(b1)
; c64.CHROUT('\n')
; c64scr.print_w(w)
; c64.CHROUT('\n')
byte nonzp1 = 42
byte nonzp2 = 42
byte nonzp3 = 42
foo.bar()
}
}
~ foo {
;~ main {
;
; sub start() {
;
; ubyte @zp ub
; byte @zp b
; word @zp w
; uword @zp uw
;
;
; byte nonzp1
; byte nonzp2
; byte nonzp3
; foo.bar()
; }
;
;}
;
;~ foo {
;
;sub bar() {
; ubyte @zp ub
; byte @zp b
; word @zp w
; uword @zp uw
;
; word nonzp1
; word nonzp2
; word nonzp3
; A=55
;}
;}
sub bar() {
ubyte @zp ub = 33
byte @zp b = 33
word @zp w = 3333
uword @zp uw = 3333
word nonzp1 = 4444
word nonzp2 = 4444
word nonzp3 = 4444
A=55
}
}

View File

@ -53,9 +53,9 @@
angle++
c64.MSIGX=0
for ubyte i in 7 to 0 step -1 {
uword x = sin8u(angle*2-i*16) as uword + 50
ubyte y = cos8u(angle*3-i*16) / 2 + 70
for ubyte @zp i in 7 to 0 step -1 {
uword @zp x = sin8u(angle*2-i*16) as uword + 50
ubyte @zp y = cos8u(angle*3-i*16) / 2 + 70
c64.SPXYW[i] = mkword(lsb(x), y)
lsl(c64.MSIGX)
if msb(x) c64.MSIGX++