mirror of
https://github.com/irmen/prog8.git
synced 2025-02-23 07:29:12 +00:00
zp allocations
This commit is contained in:
parent
204cc03fc8
commit
3a8f069854
@ -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<String>) {
|
||||
}
|
||||
|
||||
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() {
|
||||
|
@ -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")
|
||||
|
@ -155,7 +155,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
private val continueStmtLabelStack : Stack<String> = 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)
|
||||
|
@ -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<Int, Pair<String, DataType>>()
|
||||
@ -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 ?: "<unnamed>", 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 ?: "<unnamed>", 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())
|
||||
}
|
||||
|
@ -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<String> = mutableSetOf()
|
||||
}
|
||||
|
||||
val allocatedZeropageVariables = mutableMapOf<String, Pair<Int, DataType>>()
|
||||
val blocks = mutableListOf<ProgramBlock>()
|
||||
val memory = mutableMapOf<Int, List<Value>>()
|
||||
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
|
||||
|
@ -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<Double, String>()
|
||||
private val assemblyLines = mutableListOf<String>()
|
||||
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 "<<<anonymous-1>>>") to something that's allowed.
|
||||
// Also have to do that for the variablesMarkedForZeropage!
|
||||
val newblocks = mutableListOf<IntermediateProgram.ProgramBlock>()
|
||||
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")
|
||||
|
@ -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<IExpression>, position: Position, namespace:IN
|
||||
}
|
||||
|
||||
private fun builtinLen(args: List<IExpression>, 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<IExpression>, 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,
|
||||
|
@ -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) {
|
||||
|
@ -188,7 +188,7 @@ class TestZeropage {
|
||||
assertEquals(19, zp.available())
|
||||
|
||||
zp.allocate(VarDecl(VarDeclType.VAR, DataType.FLOAT, false, null, "", null, dummypos))
|
||||
assertFailsWith<CompilerException> {
|
||||
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))
|
||||
}
|
||||
@ -198,10 +198,10 @@ class TestZeropage {
|
||||
assertTrue(loc > 0)
|
||||
}
|
||||
assertEquals(0, zp.available())
|
||||
assertFailsWith<CompilerException> {
|
||||
assertFailsWith<ZeropageDepletedError> {
|
||||
zp.allocate(VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, "", null, dummypos))
|
||||
}
|
||||
assertFailsWith<CompilerException> {
|
||||
assertFailsWith<ZeropageDepletedError> {
|
||||
zp.allocate(VarDecl(VarDeclType.VAR, DataType.UWORD, false, null, "", null, dummypos))
|
||||
}
|
||||
}
|
||||
@ -221,7 +221,7 @@ class TestZeropage {
|
||||
}
|
||||
assertEquals(18,zp.available())
|
||||
|
||||
assertFailsWith<CompilerException> {
|
||||
assertFailsWith<ZeropageDepletedError> {
|
||||
// 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<CompilerException> {
|
||||
assertFailsWith<ZeropageDepletedError> {
|
||||
// no more space
|
||||
zp.allocate(VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, "", null, dummypos))
|
||||
}
|
||||
|
33
examples/fibonacci.p8
Normal file
33
examples/fibonacci.p8
Normal file
@ -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
|
||||
}
|
||||
}
|
41
examples/primes.p8
Normal file
41
examples/primes.p8
Normal file
@ -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
|
||||
}
|
||||
}
|
@ -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() {
|
||||
while true {
|
||||
A=99
|
||||
}
|
||||
|
||||
for ubyte i in 0 to 7 {
|
||||
c64.SPRPTR[i] = $0a00/64
|
||||
while true {
|
||||
A=99
|
||||
if A>100
|
||||
break
|
||||
}
|
||||
c64.SPENA = 255 ; enable all sprites
|
||||
c64utils.set_rasterirq(220) ; enable animation
|
||||
|
||||
repeat {
|
||||
A=99
|
||||
} until false
|
||||
|
||||
repeat {
|
||||
A=99
|
||||
if A>100
|
||||
break
|
||||
} until false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
~ 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++
|
||||
}
|
||||
c64.EXTCOL-=7
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
; @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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user