From 3a8f069854a565e81f00ac794d92b86e5d100457 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sun, 27 Jan 2019 01:02:45 +0100 Subject: [PATCH] zp allocations --- compiler/src/prog8/CompilerMain.kt | 12 ++- compiler/src/prog8/ast/AstChecker.kt | 13 ++- compiler/src/prog8/compiler/Compiler.kt | 3 +- compiler/src/prog8/compiler/Zeropage.kt | 28 ++++--- .../intermediate/IntermediateProgram.kt | 30 +++++++ .../src/prog8/compiler/target/c64/AsmGen.kt | 24 ++++-- .../src/prog8/functions/BuiltinFunctions.kt | 13 +-- .../prog8/optimizing/StatementOptimizer.kt | 51 +++++++++--- compiler/test/UnitTests.kt | 10 +-- examples/fibonacci.p8 | 33 ++++++++ examples/primes.p8 | 41 ++++++++++ examples/test.p8 | 79 ++++++------------- 12 files changed, 229 insertions(+), 108 deletions(-) create mode 100644 examples/fibonacci.p8 create mode 100644 examples/primes.p8 diff --git a/compiler/src/prog8/CompilerMain.kt b/compiler/src/prog8/CompilerMain.kt index 41361366b..51ce5c1d2 100644 --- a/compiler/src/prog8/CompilerMain.kt +++ b/compiler/src/prog8/CompilerMain.kt @@ -3,6 +3,7 @@ package prog8 import prog8.ast.* import prog8.compiler.* import prog8.compiler.target.c64.AsmGen +import prog8.compiler.target.c64.C64Zeropage import prog8.optimizing.constantFold import prog8.optimizing.optimizeStatements import prog8.optimizing.simplifyExpressions @@ -131,7 +132,9 @@ private fun compileMain(args: Array) { } if(writeAssembly) { - val assembly = AsmGen(compilerOptions, intermediate, heap).compileToAssembly() + val zeropage = C64Zeropage(compilerOptions) + intermediate.allocateZeropage(zeropage) + val assembly = AsmGen(compilerOptions, intermediate, heap, zeropage).compileToAssembly() assembly.assemble(compilerOptions) programname = assembly.name } @@ -176,9 +179,10 @@ fun determineCompilationOptions(moduleAst: Module): CompilationOptions { as? Directive)?.args?.single()?.int ?: 0 val zpoption: String? = (moduleAst.statements.singleOrNull { it is Directive && it.directive == "%zeropage" } as? Directive)?.args?.single()?.name?.toUpperCase() + val floatsEnabled = options.any { it.name == "enable_floats" } val zpType: ZeropageType = if (zpoption == null) - ZeropageType.KERNALSAFE + if(floatsEnabled) ZeropageType.BASICSAFE else ZeropageType.KERNALSAFE else try { ZeropageType.valueOf(zpoption) @@ -196,8 +200,8 @@ fun determineCompilationOptions(moduleAst: Module): CompilationOptions { return CompilationOptions( if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType), if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType), - zpType, zpReserved, - options.any { it.name == "enable_floats" }) + zpType, zpReserved, floatsEnabled + ) } private fun usage() { diff --git a/compiler/src/prog8/ast/AstChecker.kt b/compiler/src/prog8/ast/AstChecker.kt index 4221338fc..87269454e 100644 --- a/compiler/src/prog8/ast/AstChecker.kt +++ b/compiler/src/prog8/ast/AstChecker.kt @@ -42,9 +42,14 @@ fun printWarning(msg: String, position: Position, detailInfo: String?=null) { print("\n") else println(": $detailInfo") - print("\u001b[0m") // bright yellow + print("\u001b[0m") // normal } +fun printWarning(msg: String) { + print("\u001b[93m") // bright yellow + print("Warning: $msg") + print("\u001b[0m") // normal +} private class AstChecker(private val namespace: INameScope, private val compilerOptions: CompilationOptions, @@ -131,9 +136,6 @@ private class AstChecker(private val namespace: INameScope, } override fun process(forLoop: ForLoop): IStatement { - if(forLoop.zeropage) - println("ZEROPAGE FORLOOP $forLoop") // TODO - if(forLoop.body.isEmpty()) printWarning("for loop body is empty", forLoop.position) @@ -484,9 +486,6 @@ private class AstChecker(private val namespace: INameScope, checkResult.add(SyntaxError(msg, position ?: decl.position)) } - if(decl.zeropage) - println("ZEROPAGE VAR $decl") // TODO - // the initializer value can't refer to the variable itself (recursive definition) if(decl.value?.referencesIdentifier(decl.name) == true || decl.arrayspec?.x?.referencesIdentifier(decl.name) == true) { err("recursive var declaration") diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 7b5797ade..a9a2d158a 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -155,7 +155,7 @@ internal class Compiler(private val rootModule: Module, private val continueStmtLabelStack : Stack = Stack() fun compile(options: CompilationOptions) : IntermediateProgram { - println("\nCreating stackVM code...") + println("Creating stackVM code...") process(rootModule) return prog } @@ -2103,7 +2103,6 @@ internal class Compiler(private val rootModule: Module, prog.instr(Opcode.NOP) breakStmtLabelStack.pop() continueStmtLabelStack.pop() - } private fun translate(stmt: RepeatLoop) diff --git a/compiler/src/prog8/compiler/Zeropage.kt b/compiler/src/prog8/compiler/Zeropage.kt index e54321bdf..9121929a5 100644 --- a/compiler/src/prog8/compiler/Zeropage.kt +++ b/compiler/src/prog8/compiler/Zeropage.kt @@ -3,6 +3,9 @@ package prog8.compiler import prog8.ast.* +class ZeropageDepletedError(message: String) : Exception(message) + + abstract class Zeropage(private val options: CompilationOptions) { private val allocations = mutableMapOf>() @@ -10,6 +13,9 @@ abstract class Zeropage(private val options: CompilationOptions) { 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"} @@ -18,15 +24,15 @@ abstract class Zeropage(private val options: CompilationOptions) { if(vardecl.arrayspec!=null) { printWarning("allocating a large value (arrayspec) in zeropage", vardecl.position) when(vardecl.datatype) { - DataType.UBYTE -> (vardecl.arrayspec.x as LiteralValue).asIntegerValue!! - DataType.UWORD -> (vardecl.arrayspec.x as LiteralValue).asIntegerValue!! * 2 + 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) { - DataType.UBYTE -> 1 - DataType.UWORD -> 2 + 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) @@ -51,17 +57,17 @@ abstract class Zeropage(private val options: CompilationOptions) { } } - throw CompilerException("ERROR: no free space in ZP to allocate $size sequential bytes") + throw ZeropageDepletedError("ERROR: no free space in ZP to allocate $size sequential bytes") } protected fun reserve(range: IntRange) = free.removeAll(range) - private fun makeAllocation(location: Int, size: Int, datatype: DataType, name: String?): Int { - free.removeAll(location until location+size) - allocations[location] = Pair(name ?: "", datatype) - return location + private fun makeAllocation(address: Int, size: Int, datatype: DataType, name: String?): Int { + free.removeAll(address until address+size) + allocations[address] = Pair(name ?: "", datatype) + return address } - private fun loneByte(location: Int) = location in free && location-1 !in free && location+1 !in free - private fun sequentialFree(location: Int, size: Int) = free.containsAll((location until location+size).toList()) + private fun loneByte(address: Int) = address in free && address-1 !in free && address+1 !in free + private fun sequentialFree(address: Int, size: Int) = free.containsAll((address until address+size).toList()) } diff --git a/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt b/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt index 7acc5fee3..f25720203 100644 --- a/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt +++ b/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt @@ -3,6 +3,8 @@ package prog8.compiler.intermediate import prog8.ast.* import prog8.compiler.CompilerException import prog8.compiler.HeapValues +import prog8.compiler.Zeropage +import prog8.compiler.ZeropageDepletedError import java.io.PrintStream import java.nio.file.Path @@ -22,8 +24,10 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap get() { return variables.size } val numInstructions: Int get() { return instructions.filter { it.opcode!= Opcode.LINE }.size } + val variablesMarkedForZeropage: MutableSet = mutableSetOf() } + val allocatedZeropageVariables = mutableMapOf>() val blocks = mutableListOf() val memory = mutableMapOf>() private lateinit var currentBlock: ProgramBlock @@ -33,6 +37,30 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap val numInstructions: Int get() = blocks.sumBy { it.numInstructions } + fun allocateZeropage(zeropage: Zeropage) { + // allocates all @zp marked variables on the zeropage (for all blocks, as long as there is space in the ZP) + var notAllocated = 0 + for(block in blocks) { + val zpVariables = block.variables.filter { it.key in block.variablesMarkedForZeropage } + if (zpVariables.isNotEmpty()) { + for (variable in zpVariables) { + try { + val address = zeropage.allocate(variable.key, variable.value.type) + 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") + } + fun optimize() { println("Optimizing stackVM code...") // remove nops (that are not a label) @@ -328,6 +356,8 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap } } currentBlock.variables[scopedname] = value + if(decl.zeropage) + currentBlock.variablesMarkedForZeropage.add(scopedname) } VarDeclType.MEMORY -> { // note that constants are all folded away, but assembly code may still refer to them diff --git a/compiler/src/prog8/compiler/target/c64/AsmGen.kt b/compiler/src/prog8/compiler/target/c64/AsmGen.kt index 118715427..a80278da5 100644 --- a/compiler/src/prog8/compiler/target/c64/AsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/AsmGen.kt @@ -3,8 +3,7 @@ package prog8.compiler.target.c64 // note: to put stuff on the stack, we use Absolute,X addressing mode which is 3 bytes / 4 cycles // possible space optimization is to use zeropage (indirect),Y which is 2 bytes, but 5 cycles -import prog8.ast.DataType -import prog8.ast.escape +import prog8.ast.* import prog8.compiler.* import prog8.compiler.intermediate.* import prog8.stackvm.Syscall @@ -17,7 +16,7 @@ import kotlin.math.abs class AssemblyError(msg: String) : RuntimeException(msg) -class AsmGen(val options: CompilationOptions, val program: IntermediateProgram, val heap: HeapValues) { +class AsmGen(val options: CompilationOptions, val program: IntermediateProgram, val heap: HeapValues, val zeropage: Zeropage) { private val globalFloatConsts = mutableMapOf() private val assemblyLines = mutableListOf() private lateinit var block: IntermediateProgram.ProgramBlock @@ -27,9 +26,11 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram, // Because 64tass understands scoped names via .proc / .block, // we'll strip the block prefix from all scoped names in the program. // Also, convert invalid label names (such as "<<>>") to something that's allowed. + // Also have to do that for the variablesMarkedForZeropage! val newblocks = mutableListOf() for(block in program.blocks) { val newvars = block.variables.map { symname(it.key, block) to it.value }.toMap().toMutableMap() + val newvarsZeropaged = block.variablesMarkedForZeropage.map{symname(it, block)}.toMutableSet() val newlabels = block.labels.map { symname(it.key, block) to it.value}.toMap().toMutableMap() val newinstructions = block.instructions.asSequence().map { when { @@ -42,7 +43,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram, } }.toMutableList() val newConstants = block.memoryPointers.map { symname(it.key, block) to it.value }.toMap().toMutableMap() - newblocks.add(IntermediateProgram.ProgramBlock( + val newblock = IntermediateProgram.ProgramBlock( block.scopedname, block.shortname, block.address, @@ -50,7 +51,10 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram, newvars, newConstants, newlabels, - force_output = block.force_output)) + force_output = block.force_output) + newblock.variablesMarkedForZeropage.clear() + newblock.variablesMarkedForZeropage.addAll(newvarsZeropaged) + newblocks.add(newblock) } program.blocks.clear() program.blocks.addAll(newblocks) @@ -66,7 +70,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram, } fun compileToAssembly(): AssemblyProgram { - println("\nGenerating assembly code from intermediate code... ") + println("Generating assembly code from intermediate code... ") assemblyLines.clear() header() @@ -189,9 +193,12 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram, out(".cerror * > ${block.address?.toHex()}, 'block address overlaps by ', *-${block.address?.toHex()},' bytes'") out("* = ${block.address?.toHex()}") } + + // @TODO allocate variables on the zeropage as long as there is space + out("\n; memdefs and kernel subroutines") memdefs2asm(block) - out("\n; variables") + out("\n; non-zeropage variables") vardecls2asm(block) out("") @@ -219,7 +226,8 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram, } private fun vardecls2asm(block: IntermediateProgram.ProgramBlock) { - val sortedVars = block.variables.toList().sortedBy { it.second.type } + // these are the non-zeropage variables @todo is this correct now? + val sortedVars = block.variables.filter{it.key !in block.variablesMarkedForZeropage}.toList().sortedBy { it.second.type } for (v in sortedVars) { when (v.second.type) { DataType.UBYTE -> out("${v.first}\t.byte 0") diff --git a/compiler/src/prog8/functions/BuiltinFunctions.kt b/compiler/src/prog8/functions/BuiltinFunctions.kt index 5c609e64c..2e2b01722 100644 --- a/compiler/src/prog8/functions/BuiltinFunctions.kt +++ b/compiler/src/prog8/functions/BuiltinFunctions.kt @@ -52,7 +52,7 @@ val BuiltinFunctions = mapOf( "round" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArgOutputWord(a, p, n, h, Math::round) }, "floor" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArgOutputWord(a, p, n, h, Math::floor) }, "ceil" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArgOutputWord(a, p, n, h, Math::ceil) }, - "len" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", IterableDatatypes)), DataType.UBYTE, ::builtinLen), + "len" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", IterableDatatypes)), DataType.UWORD, ::builtinLen), "any" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, n, h -> collectionArgOutputBoolean(a, p, n, h) { it.any { v -> v != 0.0} }}, "all" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, n, h -> collectionArgOutputBoolean(a, p, n, h) { it.all { v -> v != 0.0} }}, "lsb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, n, h -> oneIntArgOutputInt(a, p, n, h) { x: Int -> x and 255 }}, @@ -285,6 +285,7 @@ private fun builtinAvg(args: List, position: Position, namespace:IN } private fun builtinLen(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue { + // note: in some cases the length is > 255 and so we have to return a UWORD type instead of a byte. if(args.size!=1) throw SyntaxError("len requires one argument", position) var argument = args[0].constValue(namespace, heap) @@ -299,21 +300,21 @@ private fun builtinLen(args: List, position: Position, namespace:IN return when(argument.type) { DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> { val arraySize = argument.arrayvalue?.size ?: heap.get(argument.heapId!!).arraysize - if(arraySize>255) + if(arraySize>256) throw CompilerException("array length exceeds byte limit ${argument.position}") - LiteralValue(DataType.UBYTE, bytevalue=arraySize.toShort(), position=args[0].position) + LiteralValue(DataType.UWORD, wordvalue=arraySize, position=args[0].position) } DataType.ARRAY_F -> { val arraySize = argument.arrayvalue?.size ?: heap.get(argument.heapId!!).arraysize - if(arraySize>255) + if(arraySize>256) throw CompilerException("array length exceeds byte limit ${argument.position}") - LiteralValue(DataType.UBYTE, bytevalue=arraySize.toShort(), position=args[0].position) + LiteralValue(DataType.UWORD, wordvalue=arraySize, position=args[0].position) } DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> { val str = argument.strvalue(heap) if(str.length>255) throw CompilerException("string length exceeds byte limit ${argument.position}") - LiteralValue(DataType.UBYTE, bytevalue=str.length.toShort(), position=args[0].position) + LiteralValue(DataType.UWORD, wordvalue=str.length, position=args[0].position) } DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, diff --git a/compiler/src/prog8/optimizing/StatementOptimizer.kt b/compiler/src/prog8/optimizing/StatementOptimizer.kt index 502a5c61a..acb0eff57 100644 --- a/compiler/src/prog8/optimizing/StatementOptimizer.kt +++ b/compiler/src/prog8/optimizing/StatementOptimizer.kt @@ -44,13 +44,6 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He // remove empty subroutine optimizationsDone++ statementsToRemove.add(subroutine) - } else if(subroutine.statements.size==1) { - val stmt = subroutine.statements[0] - if(stmt is ReturnFromIrq || stmt is Return) { - // remove empty subroutine - optimizationsDone++ - statementsToRemove.add(subroutine) - } } } @@ -157,6 +150,7 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He // if it calls a subroutine, // and the first instruction in the subroutine is a jump, call that jump target instead + // if the first instruction in the subroutine is a return statement, replace with a nop instruction val subroutine = functionCall.target.targetStatement(namespace) as? Subroutine if(subroutine!=null) { val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull() @@ -164,6 +158,10 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He optimizationsDone++ return FunctionCallStatement(first.identifier, functionCall.arglist, functionCall.position) } + if(first is ReturnFromIrq || first is Return) { + optimizationsDone++ + return NopStatement(functionCall.position) + } } return super.process(functionCall) @@ -172,6 +170,7 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He override fun process(functionCall: FunctionCall): IExpression { // if it calls a subroutine, // and the first instruction in the subroutine is a jump, call that jump target instead + // if the first instruction in the subroutine is a return statement with constant value, replace with the constant value val subroutine = functionCall.target.targetStatement(namespace) as? Subroutine if(subroutine!=null) { val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull() @@ -179,6 +178,11 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He optimizationsDone++ return FunctionCall(first.identifier, functionCall.arglist, functionCall.position) } + if(first is Return && first.values.size==1) { + val constval = first.values[0].constValue(namespace, heap) + if(constval!=null) + return constval + } } return super.process(functionCall) } @@ -255,8 +259,10 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He val constvalue = whileLoop.condition.constValue(namespace, heap) if(constvalue!=null) { return if(constvalue.asBooleanValue){ - // always true -> print a warning, and optimize into body + jump + // always true -> print a warning, and optimize into body + jump (if there are no continue and break statements) printWarning("condition is always true", whileLoop.position) + if(hasContinueOrBreak(whileLoop.body)) + return whileLoop val label = Label("__back", whileLoop.condition.position) whileLoop.body.statements.add(0, label) whileLoop.body.statements.add(Jump(null, @@ -284,8 +290,10 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He optimizationsDone++ repeatLoop.body } else { - // always false -> print a warning, and optimize into body + jump + // always false -> print a warning, and optimize into body + jump (if there are no continue and break statements) printWarning("condition is always false", repeatLoop.position) + if(hasContinueOrBreak(repeatLoop.body)) + return repeatLoop val label = Label("__back", repeatLoop.untilCondition.position) repeatLoop.body.statements.add(0, label) repeatLoop.body.statements.add(Jump(null, @@ -298,6 +306,31 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He return repeatLoop } + private fun hasContinueOrBreak(scope: INameScope): Boolean { + + class Searcher:IAstProcessor + { + var count=0 + + override fun process(breakStmt: Break): IStatement { + count++ + return super.process(breakStmt) + } + + override fun process(contStmt: Continue): IStatement { + count++ + return super.process(contStmt) + } + } + val s=Searcher() + for(stmt in scope.statements) { + stmt.process(s) + if(s.count>0) + return true + } + return s.count > 0 + } + override fun process(jump: Jump): IStatement { val subroutine = jump.identifier?.targetStatement(namespace) as? Subroutine if(subroutine!=null) { diff --git a/compiler/test/UnitTests.kt b/compiler/test/UnitTests.kt index d85c7c4f2..d86361fe3 100644 --- a/compiler/test/UnitTests.kt +++ b/compiler/test/UnitTests.kt @@ -188,7 +188,7 @@ class TestZeropage { assertEquals(19, zp.available()) zp.allocate(VarDecl(VarDeclType.VAR, DataType.FLOAT, false, null, "", null, dummypos)) - assertFailsWith { + assertFailsWith { // 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)) } @@ -198,10 +198,10 @@ class TestZeropage { assertTrue(loc > 0) } assertEquals(0, zp.available()) - assertFailsWith { + assertFailsWith { zp.allocate(VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, "", null, dummypos)) } - assertFailsWith { + assertFailsWith { zp.allocate(VarDecl(VarDeclType.VAR, DataType.UWORD, false, null, "", null, dummypos)) } } @@ -221,7 +221,7 @@ class TestZeropage { } assertEquals(18,zp.available()) - assertFailsWith { + assertFailsWith { // can't allocate because no more sequential bytes, only fragmented zp.allocate(VarDecl(VarDeclType.VAR, DataType.FLOAT, false, null, "", null, dummypos)) } @@ -234,7 +234,7 @@ class TestZeropage { 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)) - assertFailsWith { + assertFailsWith { // no more space zp.allocate(VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, "", null, dummypos)) } diff --git a/examples/fibonacci.p8 b/examples/fibonacci.p8 new file mode 100644 index 000000000..eaea3697f --- /dev/null +++ b/examples/fibonacci.p8 @@ -0,0 +1,33 @@ +%import c64utils + +; This example computes the first 20 values of the Fibonacci sequence. +; It uses the feature that variables that don't have an initialization value, +; will retain their previous value over multiple invocations of the program or subroutine. +; This is extremely handy for the Fibonacci sequence because it is defined +; in terms of 'the next value is the sum of the previous two values' + +~ main { + sub start() { + c64scr.print("fibonacci sequence\n") + fib_setup() + for ubyte i in 0 to 20 { + c64scr.print_uw(fib_next()) + c64.CHROUT('\n') + } + } + + sub fib_setup() { + ; (re)start the sequence + main.fib_next.prev=0 + main.fib_next.current=1 + } + + sub fib_next() -> uword { + uword prev ; no init value so will retain previous value + uword current ; no init value so will retain previous value + uword new = current + prev + prev = current + current = new + return prev + } +} diff --git a/examples/primes.p8 b/examples/primes.p8 new file mode 100644 index 000000000..443acf5b5 --- /dev/null +++ b/examples/primes.p8 @@ -0,0 +1,41 @@ +%import c64utils + +~ main { + + + sub start() { + ; clear the sieve, and mark 0 and 1 as not prime. + memset(sieve, 256, false) + sieve[0] = true + sieve[1] = true + + ; calculate primes + c64scr.print("prime numbers up to 255:\n\n") + while true { + ubyte prime = find_next_prime() + if prime==0 + break + c64scr.print_ub(prime) + c64scr.print(", ") + } + c64.CHROUT('\n') + } + + ubyte[256] sieve + + sub find_next_prime() -> ubyte { + for ubyte prime in 2 to 255 { + if not sieve[prime] { + ; found one, mark the multiples and return it. + sieve[prime] = true + uword multiple = prime**2 + while multiple < len(sieve) { + sieve[lsb(multiple)] = true + multiple += prime + } + return prime + } + } + return 0 + } +} diff --git a/examples/test.p8 b/examples/test.p8 index 9807140a1..a752922c2 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,68 +1,35 @@ %import c64utils -%import c64lib - - -~ spritedata $0a00 { - ; this memory block contains the sprite data - ; it must start on an address aligned to 64 bytes. - %option force_output ; make sure the data in this block appears in the resulting program - - ubyte[63] balloonsprite = [ %00000000,%01111111,%00000000, - %00000001,%11111111,%11000000, - %00000011,%11111111,%11100000, - %00000011,%11100011,%11100000, - %00000111,%11011100,%11110000, - %00000111,%11011101,%11110000, - %00000111,%11011100,%11110000, - %00000011,%11100011,%11100000, - %00000011,%11111111,%11100000, - %00000011,%11111111,%11100000, - %00000010,%11111111,%10100000, - %00000001,%01111111,%01000000, - %00000001,%00111110,%01000000, - %00000000,%10011100,%10000000, - %00000000,%10011100,%10000000, - %00000000,%01001001,%00000000, - %00000000,%01001001,%00000000, - %00000000,%00111110,%00000000, - %00000000,%00111110,%00000000, - %00000000,%00111110,%00000000, - %00000000,%00011100,%00000000 ] -} ~ main { sub start() { - - for ubyte i in 0 to 7 { - c64.SPRPTR[i] = $0a00/64 + while true { + A=99 } - c64.SPENA = 255 ; enable all sprites - c64utils.set_rasterirq(220) ; enable animation - } -} - -~ irq { - - sub irq() { - ubyte@zp angle ; no initialization value so it keeps the previous one. - - c64.EXTCOL-- - - angle++ - c64.MSIGX=0 - - 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++ - c64.EXTCOL++ + while true { + A=99 + if A>100 + break } - c64.EXTCOL-=7 + repeat { + A=99 + } until false + + repeat { + A=99 + if A>100 + break + } until false } + + ; @todo code for pow() + + ; @todo optimize code generation for "if blah ..." and "if not blah ..." + + ; @todo optimize vm + ; push_byte ub:01 + ; jnz _prog8stmt_7_loop }