Skip to content

Commit

Permalink
finalized zeropage variable allocation
Browse files Browse the repository at this point in the history
  • Loading branch information
irmen committed Jan 27, 2019
1 parent 0219c69 commit 7459896
Show file tree
Hide file tree
Showing 16 changed files with 124 additions and 187 deletions.
6 changes: 3 additions & 3 deletions compiler/src/prog8/ast/AstChecker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/prog8/ast/AstRecursionChecker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/prog8/ast/StmtReorderer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
35 changes: 13 additions & 22 deletions compiler/src/prog8/compiler/Zeropage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.

fun available() = free.size
val allowedDatatypes = NumericDatatypes

fun allocate(name: String, type: DataType) =
allocate(VarDecl(VarDeclType.VAR, type, true, null, name, null, Position("",0,0,0)))
fun available() = free.size

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,15 @@ 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++
}
}
}
}
println("DEBUG: ${allocatedZeropageVariables.size} variables allocated in Zeropage") // TODO
if(notAllocated>0)
printWarning("$notAllocated variables marked for Zeropage could not be allocated there")
}
Expand Down
59 changes: 46 additions & 13 deletions compiler/src/prog8/compiler/target/c64/AsmGen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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}) {
Expand Down Expand Up @@ -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=="-")
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 0 additions & 4 deletions compiler/src/prog8/compiler/target/c64/Commodore64.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/prog8/functions/BuiltinFunctions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion compiler/src/prog8/parser/ModuleParsing.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
65 changes: 29 additions & 36 deletions compiler/test/UnitTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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(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("varname", DataType.UBYTE, null)
}
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
Expand Down Expand Up @@ -187,72 +180,72 @@ 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)
}
}

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

@Test
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())
}
}
Expand Down
1 change: 0 additions & 1 deletion compiler/test/ValueOperationsTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.*


Expand Down
Loading

0 comments on commit 7459896

Please sign in to comment.