avoid zp corruption issues and added zp mode for floating point (todo: allocate)

This commit is contained in:
Irmen de Jong 2019-02-03 00:14:56 +01:00
parent ae21e03e1d
commit 7ff1af3934
33 changed files with 217 additions and 83 deletions

View File

@ -182,7 +182,7 @@ fun determineCompilationOptions(moduleAst: Module): CompilationOptions {
val floatsEnabled = options.any { it.name == "enable_floats" }
val zpType: ZeropageType =
if (zpoption == null)
if(floatsEnabled) ZeropageType.BASICSAFE else ZeropageType.KERNALSAFE
if(floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
else
try {
ZeropageType.valueOf(zpoption)

View File

@ -174,9 +174,9 @@ interface IAstProcessor {
return functionCall
}
fun process(functionCall: FunctionCallStatement): IStatement {
functionCall.arglist = functionCall.arglist.map { it.process(this) }.toMutableList()
return functionCall
fun process(functionCallStatement: FunctionCallStatement): IStatement {
functionCallStatement.arglist = functionCallStatement.arglist.map { it.process(this) }.toMutableList()
return functionCallStatement
}
fun process(identifier: IdentifierReference): IExpression {

View File

@ -592,9 +592,10 @@ private class AstChecker(private val namespace: INameScope,
if(directive.parent !is Module) err("this directive may only occur at module level")
if(directive.args.size!=1 ||
directive.args[0].name != "basicsafe" &&
directive.args[0].name != "floatsafe" &&
directive.args[0].name != "kernalsafe" &&
directive.args[0].name != "full")
err("invalid zp type, expected basicsafe, kernalsafe, or full")
err("invalid zp type, expected basicsafe, floatsafe, kernalsafe, or full")
}
"%zpreserved" -> {
if(directive.parent !is Module) err("this directive may only occur at module level")
@ -696,7 +697,7 @@ private class AstChecker(private val namespace: INameScope,
}
"and", "or", "xor", "&", "|", "^" -> {
// only integer numeric operands accepted
val rightDt = expr.right?.resultingDatatype(namespace, heap)
val rightDt = expr.right.resultingDatatype(namespace, heap)
val leftDt = expr.left.resultingDatatype(namespace, heap)
if(leftDt !in IntegerDatatypes || rightDt !in IntegerDatatypes)
checkResult.add(ExpressionError("logical or bitwise operator can only be used on integer operands", expr.right.position))
@ -776,13 +777,13 @@ private class AstChecker(private val namespace: INameScope,
return super.process(functionCall)
}
override fun process(functionCall: FunctionCallStatement): IStatement {
val targetStatement = checkFunctionOrLabelExists(functionCall.target, functionCall)
override fun process(functionCallStatement: FunctionCallStatement): IStatement {
val targetStatement = checkFunctionOrLabelExists(functionCallStatement.target, functionCallStatement)
if(targetStatement!=null)
checkFunctionCall(targetStatement, functionCall.arglist, functionCall.position)
checkFunctionCall(targetStatement, functionCallStatement.arglist, functionCallStatement.position)
if(targetStatement is Subroutine && targetStatement.returntypes.isNotEmpty())
printWarning("result value of subroutine call is discarded", functionCall.position)
return super.process(functionCall)
printWarning("result value of subroutine call is discarded", functionCallStatement.position)
return super.process(functionCallStatement)
}
private fun checkFunctionCall(target: IStatement, args: List<IExpression>, position: Position) {

View File

@ -92,9 +92,9 @@ private class AstRecursionChecker(private val namespace: INameScope) : IAstProce
return listOf(AstException("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain"))
}
override fun process(functionCall: FunctionCallStatement): IStatement {
val scope = functionCall.definingScope()
val targetStatement = functionCall.target.targetStatement(namespace)
override fun process(functionCallStatement: FunctionCallStatement): IStatement {
val scope = functionCallStatement.definingScope()
val targetStatement = functionCallStatement.target.targetStatement(namespace)
if(targetStatement!=null) {
val targetScope = when (targetStatement) {
is Subroutine -> targetStatement
@ -102,7 +102,7 @@ private class AstRecursionChecker(private val namespace: INameScope) : IAstProce
}
callGraph.add(scope, targetScope)
}
return super.process(functionCall)
return super.process(functionCallStatement)
}
override fun process(functionCall: FunctionCall): IExpression {

View File

@ -38,10 +38,10 @@ private class StatementReorderer(private val namespace: INameScope, private val
.filter { it.value is Block && !(it.value as Block).isInLibrary }
.map { it.index to it.value }
.reversed()
for(blocks in nonLibraryBlocks)
module.statements.removeAt(blocks.first)
for(blocks in nonLibraryBlocks)
module.statements.add(0, blocks.second)
for(nonLibBlock in nonLibraryBlocks)
module.statements.removeAt(nonLibBlock.first)
for(nonLibBlock in nonLibraryBlocks)
module.statements.add(0, nonLibBlock.second)
val mainBlock = module.statements.single { it is Block && it.name=="main" }
if((mainBlock as Block).address==null) {
module.statements.remove(mainBlock)

View File

@ -132,6 +132,7 @@ enum class LauncherType {
enum class ZeropageType {
BASICSAFE,
FLOATSAFE,
KERNALSAFE,
FULL
}

View File

@ -6,7 +6,7 @@ import prog8.ast.*
class ZeropageDepletedError(message: String) : Exception(message)
abstract class Zeropage(private val options: CompilationOptions) {
abstract class Zeropage(protected val options: CompilationOptions) {
private val allocations = mutableMapOf<Int, Pair<String, DataType>>()
val free = mutableListOf<Int>() // subclasses must set this to the appropriate free locations.
@ -61,4 +61,12 @@ abstract class Zeropage(private val options: CompilationOptions) {
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())
enum class ExitProgramStrategy {
CLEAN_EXIT,
SYSTEM_RESET
}
abstract val exitProgramStrategy: ExitProgramStrategy
}

View File

@ -194,7 +194,15 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
out(" jsr ${block.scopedname}.${initVarsLabel.name}")
}
out(" clc")
when(zeropage.exitProgramStrategy) {
Zeropage.ExitProgramStrategy.CLEAN_EXIT -> {
out(" jmp main.start\t; jump to program entrypoint")
}
Zeropage.ExitProgramStrategy.SYSTEM_RESET -> {
out(" jsr main.start\t; call program entrypoint")
out(" jmp (c64.RESET_VEC)\t; cold reset")
}
}
out("")
// the global list of all floating point constants for the whole program

View File

@ -33,7 +33,16 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
const val SCRATCH_W2 = 0xfd // $fd/$fe
}
override val exitProgramStrategy: ExitProgramStrategy = when(options.zeropage) {
ZeropageType.BASICSAFE, ZeropageType.FLOATSAFE -> ExitProgramStrategy.CLEAN_EXIT
ZeropageType.KERNALSAFE, ZeropageType.FULL -> ExitProgramStrategy.SYSTEM_RESET
}
init {
if(options.floats && options.zeropage!=ZeropageType.FLOATSAFE && options.zeropage!=ZeropageType.BASICSAFE)
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe'")
if(options.zeropage == ZeropageType.FULL) {
free.addAll(0x04 .. 0xf9)
free.add(0xff)
@ -47,6 +56,9 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53, 0x6f, 0x70))
}
else if(options.zeropage == ZeropageType.FLOATSAFE) {
TODO("reserve float zp locations")
}
// add the other free Zp addresses
// these are valid for the C-64 (when no RS232 I/O is performed):
free.addAll(listOf(0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0d, 0x0e,

View File

@ -114,39 +114,39 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
}
override fun process(functionCall: FunctionCallStatement): IStatement {
if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0] in BuiltinFunctions) {
val functionName = functionCall.target.nameInSource[0]
override fun process(functionCallStatement: FunctionCallStatement): IStatement {
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) {
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in pureBuiltinFunctions) {
printWarning("statement has no effect (function return value is discarded)", functionCall.position)
statementsToRemove.add(functionCall)
return functionCall
printWarning("statement has no effect (function return value is discarded)", functionCallStatement.position)
statementsToRemove.add(functionCallStatement)
return functionCallStatement
}
}
if(functionCall.target.nameInSource==listOf("c64scr", "print") ||
functionCall.target.nameInSource==listOf("c64scr", "print_p")) {
if(functionCallStatement.target.nameInSource==listOf("c64scr", "print") ||
functionCallStatement.target.nameInSource==listOf("c64scr", "print_p")) {
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
if(functionCall.arglist.single() is LiteralValue)
if(functionCallStatement.arglist.single() is LiteralValue)
throw AstException("string argument should be on heap already")
val stringVar = functionCall.arglist.single() as? IdentifierReference
val stringVar = functionCallStatement.arglist.single() as? IdentifierReference
if(stringVar!=null) {
val heapId = stringVar.heapId(namespace)
val string = heap.get(heapId).str!!
if(string.length==1) {
val petscii = Petscii.encodePetscii(string, true)[0]
functionCall.arglist.clear()
functionCall.arglist.add(LiteralValue.optimalInteger(petscii, functionCall.position))
functionCall.target = IdentifierReference(listOf("c64", "CHROUT"), functionCall.target.position)
functionCallStatement.arglist.clear()
functionCallStatement.arglist.add(LiteralValue.optimalInteger(petscii, functionCallStatement.position))
functionCallStatement.target = IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position)
optimizationsDone++
return functionCall
return functionCallStatement
} else if(string.length==2) {
val petscii = Petscii.encodePetscii(string, true)
val scope = AnonymousScope(mutableListOf(), functionCall.position)
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCall.target.position),
mutableListOf(LiteralValue.optimalInteger(petscii[0], functionCall.position)), functionCall.position))
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCall.target.position),
mutableListOf(LiteralValue.optimalInteger(petscii[1], functionCall.position)), functionCall.position))
val scope = AnonymousScope(mutableListOf(), functionCallStatement.position)
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
mutableListOf(LiteralValue.optimalInteger(petscii[0], functionCallStatement.position)), functionCallStatement.position))
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
mutableListOf(LiteralValue.optimalInteger(petscii[1], functionCallStatement.position)), functionCallStatement.position))
optimizationsDone++
return scope
}
@ -156,20 +156,20 @@ 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
val subroutine = functionCallStatement.target.targetStatement(namespace) as? Subroutine
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Jump && first.identifier!=null) {
optimizationsDone++
return FunctionCallStatement(first.identifier, functionCall.arglist, functionCall.position)
return FunctionCallStatement(first.identifier, functionCallStatement.arglist, functionCallStatement.position)
}
if(first is ReturnFromIrq || first is Return) {
optimizationsDone++
return NopStatement(functionCall.position)
return NopStatement(functionCallStatement.position)
}
}
return super.process(functionCall)
return super.process(functionCallStatement)
}
override fun process(functionCall: FunctionCall): IExpression {

View File

@ -60,16 +60,20 @@ Directives
- style ``kernalsafe`` -- use the part of the ZP that is 'free' or only used by BASIC routines,
and don't change anything else. This allows full use of KERNAL ROM routines (but not BASIC routines),
including default IRQs during normal system operation.
When the program exits, a system reset is performed (because BASIC will be in a corrupt state).
- style ``floatsafe`` -- like the previous one but also reserves the addresses that
are required to perform floating point operations (from the BASIC kernel). No clean exit is possible.
- style ``basicsafe`` -- the most restricted mode; only use the handful 'free' addresses in the ZP, and don't
touch change anything else. This allows full use of BASIC and KERNAL ROM routines including default IRQs
during normal system operation.
When the program exits, it simply returns to the BASIC ready prompt.
- style ``full`` -- claim the whole ZP for variables for the program, overwriting everything,
except the few addresses mentioned above that are used by the system's IRQ routine.
Even though the default IRQ routine is still active, it is impossible to use most BASIC and KERNAL ROM routines.
This includes many floating point operations and several utility routines that do I/O, such as ``print_string``.
It is also not possible to cleanly exit the program, other than resetting the machine.
This option makes programs smaller and faster because many more variables can
be stored in the ZP, which is more efficient.
As with ``kernalsafe``, it is not possible to cleanly exit the program, other than to reset the machine.
This option makes programs smaller and faster because even more variables can
be stored in the ZP (which allows for more efficient assembly code).
Also read :ref:`zeropage`.

View File

@ -77,11 +77,6 @@ Theoretically they can all be used in a program, with the follwoing limitations:
- it's more convenient and safe to let the compiler allocate these addresses for you and just
use symbolic names in the program code.
.. todo::
There must be a way to tell the compiler which variables you require to be in Zeropage:
``zeropage`` modifier keyword on vardecl perhaps?
Prog8 knows what addresses are safe to use in the various ZP handling configurations.
It will use the free ZP addresses to place its ZP variables in,
until they're all used up. If instructed to output a program that takes over the entire
@ -93,6 +88,7 @@ treats the ZP for the program. The default is to be reasonably restrictive to us
part of the ZP that is not used by the C64's kernal routines.
It's possible to claim the whole ZP as well (by disabling the operating system or kernal).
If you want, it's also possible to be more restricive and stay clear of the addresses used by BASIC routines too.
This allows the program to exit cleanly back to a BASIC ready prompt - something that is not possible in the other modes.
IRQs and the ZeroPage

View File

@ -1,4 +1,6 @@
%import c64utils
%zeropage basicsafe
~ main {

View File

@ -1,5 +1,6 @@
%import c64utils
%import c64flt
%zeropage basicsafe
~ main {

View File

@ -1,4 +1,5 @@
%import c64utils
%zeropage basicsafe
~ main {

View File

@ -1,4 +1,5 @@
%import c64utils
%zeropage basicsafe
~ main {

View File

@ -1,4 +1,5 @@
%import c64utils
%zeropage basicsafe
~ main {

View File

@ -1,4 +1,5 @@
%import c64utils
%zeropage basicsafe
~ main {

View File

@ -1,5 +1,6 @@
%import c64utils
%import c64flt
%zeropage basicsafe
~ main {

View File

@ -1,4 +1,5 @@
%import c64utils
%zeropage basicsafe
~ main {

View File

@ -1,4 +1,5 @@
%import c64utils
%zeropage basicsafe
~ main {

View File

@ -1,4 +1,5 @@
%import c64utils
%zeropage basicsafe
~ main {

View File

@ -1,4 +1,5 @@
%import c64utils
%zeropage basicsafe
; This example computes the first 20 values of the Fibonacci sequence.
; It uses the feature that variables that don't have an initialization value,

View File

@ -1,6 +1,7 @@
%import c64lib
%import c64utils
%import c64flt
%zeropage basicsafe
~ main {

View File

@ -1,5 +1,6 @@
%import c64utils
%import c64lib
%zeropage basicsafe
; The classic number guessing game.
; This version uses mostly high level subroutine calls and loops.

View File

@ -1,4 +1,5 @@
%import c64utils
%zeropage basicsafe
~ main {

View File

@ -1,5 +1,6 @@
%import c64utils
%import c64lib
%zeropage basicsafe
~ spritedata $0a00 {

100
examples/tehtriz.p8 Normal file
View File

@ -0,0 +1,100 @@
~ main {
const ubyte boardOffsetX = 14
const ubyte boardOffsetY = 3
const ubyte boardWidth = 10
const ubyte boardHeight = 20
; 3x3, rotating around their center square:
ubyte[4] blockJ = [0, 4, 5, 6]
ubyte[4] blockL = [2, 4, 5, 6]
ubyte[4] blockS = [1, 2, 4, 5]
ubyte[4] blockT = [1, 4, 5, 6]
ubyte[4] blockZ = [0, 1, 5, 6]
;4x4, rotating around center:
ubyte[4] blockI = [4, 5, 6, 7]
ubyte[4] blockO = [1, 2, 5, 6]
; block colors I, J, L, O, S, T, Z: cyan, blue, orange, yellow, green, purple, red
ubyte[7] blockColors = [3, 6, 8, 7, 5, 4, 2]
ubyte[7] blockSizes = [4, 3, 3, 4, 3, 3, 3] ; needed for proper rotation? (or just use block num?)
ubyte[16] currentBlock
ubyte currentBlockSize ; 3 or 4
ubyte currentBlockNum
sub start() {
drawBoard()
for ubyte b in 7 to 0 step -1 {
newCurrentBlock(b)
drawBlock(3, 2+b*3, 102) ; 102 = stipple
drawBlock(boardOffsetX+3, 1+b*3, 160) ; 160 = block, 32 = erase (space)
}
while(true) {
; loop
}
}
sub drawBoard() {
c64scr.PLOT(1,1)
c64scr.print("teh▁triz")
c64scr.setcc(boardOffsetX-1, boardOffsetY+boardHeight, 124, 12)
c64scr.setcc(boardOffsetX+boardWidth, boardOffsetY+boardHeight, 126, 12)
ubyte i
for i in boardOffsetX+boardWidth-1 to boardOffsetX step -1
c64scr.setcc(i, boardOffsetY+boardHeight, 69, 11)
for i in boardOffsetY+boardHeight-1 to boardOffsetY step -1 {
c64scr.setcc(boardOffsetX-1, i, 89, 11)
c64scr.setcc(boardOffsetX+boardWidth, i, 84, 11)
}
}
sub newCurrentBlock(ubyte block) {
memset(currentBlock, len(currentBlock), 0)
currentBlockNum = block
currentBlockSize = blockSizes[block]
; @todo would be nice to have an explicit pointer type to reference the array, and code the loop only once...
ubyte blockCol = blockColors[block]
ubyte i
if block==0 { ; I
for i in blockI
currentBlock[i] = blockCol
}
else if block==1 { ; J
for i in blockJ
currentBlock[i] = blockCol
}
else if block==2 { ; L
for i in blockL
currentBlock[i] = blockCol
}
else if block==3 { ; O
for i in blockO
currentBlock[i] = blockCol
}
else if block==4 { ; S
for i in blockS
currentBlock[i] = blockCol
}
else if block==5 { ; T
for i in blockT
currentBlock[i] = blockCol
}
else if block==6 { ; Z
for i in blockZ
currentBlock[i] = blockCol
}
}
sub drawBlock(ubyte x, ubyte y, ubyte character) {
for ubyte i in 15 to 0 step -1 {
ubyte c=currentBlock[i]
if c
c64scr.setcc((i&3)+x, (i/4)+y, character, c)
}
}
}

View File

@ -1,34 +1,20 @@
%import c64utils
%zeropage basicsafe
~ main {
; @todo fucks up basic - a few list: will corrupt the interpreter
ubyte dummy
ubyte dummy2
sub start() {
foo(1)
bar(1,2)
baz(3333)
bzaz(60000)
}
sub foo(byte arg) {
byte local = arg
A=44
}
sub bar(byte arg1, ubyte arg2) {
byte local1 = arg1
ubyte local2 = arg2
A=44
}
sub baz(word arg) {
word local=arg
A=44
}
sub bzaz(uword arg) {
uword local=arg
A=44
ubyte qq
ubyte qq2
ubyte qq3
ubyte qq4
ubyte qq5
c64scr.setcc(13, 10, 89, 11)
}
}

View File

@ -1,5 +1,6 @@
%import c64utils
%import c64lib
%zeropage basicsafe
~ spritedata $0a00 {

View File

@ -1,4 +1,4 @@
// Generated from ./parser/antlr/prog8.g4 by ANTLR 4.7.2
// Generated from prog8.g4 by ANTLR 4.7.2
package prog8.parser;

View File

@ -1,4 +1,4 @@
// Generated from ./parser/antlr/prog8.g4 by ANTLR 4.7.2
// Generated from prog8.g4 by ANTLR 4.7.2
package prog8.parser;

View File

@ -17,5 +17,6 @@
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Python 3.7 interpreter library" level="application" />
</component>
</module>