allow floats in ZP, if_pos and if_neg added, ZP allocations more flexible

This commit is contained in:
Irmen de Jong 2017-12-31 00:48:00 +01:00
parent e67e4c0b13
commit a5283bfc7b
7 changed files with 183 additions and 42 deletions

View File

@ -13,7 +13,7 @@ import datetime
import subprocess
import contextlib
from functools import partial
from typing import TextIO, Set, Union, List, Tuple, Callable
from typing import TextIO, Set, Union, List, Callable
from .parse import ProgramFormat, ParseResult, Parser
from .symbols import Zeropage, DataType, ConstantDef, VariableDef, SubroutineDef, \
STRING_DATATYPES, REGISTER_WORDS, REGISTER_BYTES, FLOAT_MAX_NEGATIVE, FLOAT_MAX_POSITIVE
@ -127,6 +127,7 @@ class CodeGenerator:
must_save_zp = self.parsed.clobberzp and self.parsed.restorezp
if must_save_zp:
self.p("\t\tjsr il65_lib_zp.save_zeropage")
zp_float_bytes = {}
# Only the vars from the ZeroPage need to be initialized here,
# the vars in all other blocks are just defined and pre-filled there.
zpblocks = [b for b in self.parsed.blocks if b.name == "ZP"]
@ -157,7 +158,16 @@ class CodeGenerator:
self.p("\t\tsta {:s}".format(vname))
self.p("\t\tstx {:s}+1".format(vname))
elif variable.type == DataType.FLOAT:
raise CodeError("floats cannot be stored in the zp")
bytes = self.to_mflpt5(vvalue) # type: ignore
zp_float_bytes[variable.name] = (vname, bytes, vvalue)
if zp_float_bytes:
self.p("\t\tldx #4")
self.p("-")
for varname, (vname, b, fv) in zp_float_bytes.items():
self.p("\t\tlda _float_bytes_{:s},x".format(varname))
self.p("\t\tsta {:s},x".format(vname))
self.p("\t\tdex")
self.p("\t\tbpl -")
self.p("; end init zp vars")
else:
self.p("\t\t; there are no zp vars to initialize")
@ -170,6 +180,10 @@ class CodeGenerator:
self.p("\t\tjmp il65_lib_zp.restore_zeropage")
else:
self.p("\t\tjmp {:s}.start\t\t; call user code".format(main_block_label))
self.p("")
for varname, (vname, bytes, fpvalue) in zp_float_bytes.items():
self.p("_float_bytes_{:s}\t\t.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}\t; {}".format(varname, *bytes, fpvalue))
self.p("\n")
def blocks(self) -> None:
# if there's a <header> block, it always goes second
@ -634,6 +648,10 @@ class CodeGenerator:
self.p("\t\tbeq " + targetstr)
elif ifs in ("cc", "cs", "vc", "vs", "eq", "ne"):
self.p("\t\tb{:s} {:s}".format(ifs, targetstr))
elif ifs == "pos":
self.p("\t\tbpl " + targetstr)
elif ifs == "neg":
self.p("\t\tbmi " + targetstr)
elif ifs == "lt":
self.p("\t\tbcc " + targetstr)
elif ifs == "gt":
@ -1463,19 +1481,18 @@ class CodeGenerator:
raise CodeError("invalid assignment value type", str(stmt))
def generate_assign_float_to_mem(self, mmv: ParseResult.MemMappedValue,
rvalue: Union[ParseResult.FloatValue, ParseResult.IntegerValue], save_reg: bool=True) -> None:
rvalue: Union[ParseResult.FloatValue, ParseResult.IntegerValue]) -> None:
floatvalue = float(rvalue.value)
mflpt = self.to_mflpt5(floatvalue)
target = mmv.name or Parser.to_hex(mmv.address)
if save_reg:
self.p("\t\tpha\t\t\t; {:s} = {}".format(target, rvalue.name or floatvalue))
else:
self.p("\t\t\t\t\t; {:s} = {}".format(target, rvalue.name or floatvalue))
for num in range(5):
self.p("\t\tlda #${:02x}".format(mflpt[num]))
self.p("\t\tsta {:s}+{:d}".format(target, num))
if save_reg:
self.p("\t\tpla")
self.p("\t\tpha\t\t\t; {:s} = {}".format(target, rvalue.name or floatvalue))
a_reg_value = None
for i, byte in enumerate(mflpt):
if byte != a_reg_value:
self.p("\t\tlda #${:02x}".format(byte))
a_reg_value = byte
self.p("\t\tsta {:s}+{:d}".format(target, i))
self.p("\t\tpla")
def generate_assign_reg_to_memory(self, lv: ParseResult.MemMappedValue, r_register: str) -> None:
# Memory = Register
@ -1680,7 +1697,7 @@ class CodeGenerator:
elif lvdatatype == DataType.FLOAT:
if rvalue.value is not None and not DataType.FLOAT.assignable_from_value(rvalue.value):
raise CodeError("value cannot be assigned to a float")
self.generate_assign_float_to_mem(lv, rvalue, False)
self.generate_assign_float_to_mem(lv, rvalue)
else:
raise CodeError("invalid lvalue type " + str(lvdatatype))

View File

@ -508,7 +508,7 @@ class ParseResult:
">=": "<=",
"<": ">",
">": "<"}
IF_STATUSES = {"cc", "cs", "vc", "vs", "eq", "ne", "true", "not", "zero", "lt", "gt", "le", "ge"}
IF_STATUSES = {"cc", "cs", "vc", "vs", "eq", "ne", "true", "not", "zero", "pos", "neg", "lt", "gt", "le", "ge"}
def __init__(self, ifstatus: str, leftvalue: Optional['ParseResult.Value'],
operator: str, rightvalue: Optional['ParseResult.Value'], lineno: int) -> None:

View File

@ -212,39 +212,56 @@ class Zeropage:
SCRATCH_W2 = 0xfd # $fd/$fe
def __init__(self) -> None:
self.unused_bytes = [] # type: List[int]
self.unused_words = [] # type: List[int]
self.free = [] # type: List[int]
self.allocations = {} # type: Dict[int, Tuple[str, DataType]]
self._configured = False
def configure(self, clobber_zp: bool = False) -> None:
if self._configured:
raise SymbolError("cannot configure the ZP multiple times")
if clobber_zp:
self.unused_bytes = list(range(0x04, 0x80)) + [0xff]
self.unused_words = list(range(0x80, 0xfb, 2))
self.free = list(range(0x04, 0xfb)) + [0xff]
else:
# these are valid for the C-64 (when no RS232 I/O is performed):
# ($02, $03, $fb-$fc, $fd-$fe are reserved as scratch addresses for various routines)
self.unused_bytes = [0x04, 0x05, 0x06, 0x2a, 0x52] # 5 zp variables (1 byte each)
self.unused_words = [0xf7, 0xf9] # 2 zp word variables (2 bytes each)
# @todo more clever allocating, don't have fixed bytes and words
assert self.SCRATCH_B1 not in self.unused_bytes and self.SCRATCH_B1 not in self.unused_words
assert self.SCRATCH_B2 not in self.unused_bytes and self.SCRATCH_B2 not in self.unused_words
self.free = [0x04, 0x05, 0x06, 0x2a, 0x52, 0xf7, 0xf8, 0xf9, 0xfa]
assert self.SCRATCH_B1 not in self.free
assert self.SCRATCH_B2 not in self.free
assert self.SCRATCH_W1 not in self.free
assert self.SCRATCH_W2 not in self.free
self._configured = True
def get_unused_byte(self):
return self.unused_bytes.pop()
def allocate(self, name: str, datatype: DataType) -> int:
assert self._configured
size = {
DataType.BYTE: 1,
DataType.WORD: 2,
DataType.FLOAT: 5
}[datatype]
def get_unused_word(self):
return self.unused_words.pop()
def sequential(loc: int) -> bool:
for i in range(size):
if loc+i not in self.free:
return False
return True
@property
def available_byte_vars(self) -> int:
return len(self.unused_bytes)
if len(self.free) > 0:
if size == 1:
assert not name or name not in {a[0] for a in self.allocations.values()}
loc = self.free.pop()
self.allocations[loc] = (name or "<unnamed>", datatype)
return loc
for candidate in range(min(self.free), max(self.free)+1):
if sequential(candidate):
assert not name or name not in {a[0] for a in self.allocations.values()}
for loc in range(candidate, candidate+size):
self.free.remove(loc)
self.allocations[candidate] = (name or "<unnamed>", datatype)
return candidate
raise LookupError("no more free space in ZP to allocate {:d} sequential bytes".format(size))
@property
def available_word_vars(self) -> int:
return len(self.unused_words)
def available(self) -> int:
return len(self.free)
class SymbolTable:
@ -368,22 +385,25 @@ class SymbolTable:
if datatype == DataType.BYTE:
if allocate and self.name == "ZP":
try:
address = self._zeropage.get_unused_byte()
address = self._zeropage.allocate(name, datatype)
except LookupError:
raise SymbolError("no space in ZP left for more global 8-bit variables (try zp clobber)")
raise SymbolError("no space in ZP left for global 8-bit variable (try zp clobber)")
self.symbols[name] = VariableDef(self.name, name, sourceref, DataType.BYTE, allocate,
value=value, length=1, address=address)
elif datatype == DataType.WORD:
if allocate and self.name == "ZP":
try:
address = self._zeropage.get_unused_word()
address = self._zeropage.allocate(name, datatype)
except LookupError:
raise SymbolError("no space in ZP left for more global 16-bit variables (try zp clobber)")
raise SymbolError("no space in ZP left for global 16-bit word variable (try zp clobber)")
self.symbols[name] = VariableDef(self.name, name, sourceref, DataType.WORD, allocate,
value=value, length=1, address=address)
elif datatype == DataType.FLOAT:
if allocate and self.name == "ZP":
raise SymbolError("floats cannot be stored in the ZP")
try:
address = self._zeropage.allocate(name, datatype)
except LookupError:
raise SymbolError("no space in ZP left for global 5-byte MFLT float variable (try zp clobber)")
self.symbols[name] = VariableDef(self.name, name, sourceref, DataType.FLOAT, allocate,
value=value, length=1, address=address)
elif datatype == DataType.BYTEARRAY:

View File

@ -106,6 +106,11 @@ machine, in which case all of the zero page locations are suddenly available for
IL65 can generate a special routine that saves and restores the zero page to let your program run
and return safely back to the system afterwards - you don't have to take care of that yourself.
@todo some global way (in ZP block) to promote certian other blocks/variables from that block or even
subroutine to the zeropage. Don't do this in the block itself because it's a global optimization
and if blocks require it themselves you can't combine various modules anymore once ZP runs out.
DATA TYPES
----------
@ -361,8 +366,9 @@ that is translated into a comparison (if needed) and then a conditional branch i
if[_XX] [<expression>] goto <label>
The if-status XX is one of: [cc, cs, vc, vs, eq, ne, true, not, zero, lt, gt, le, ge]
It defaults to 'true' (=='ne', not-zero) if omitted. @todo signed: pos, neg, lts==neg?, gts==eq+pos?, les==neg+eq?, ges==pos?
The if-status XX is one of: [cc, cs, vc, vs, eq, ne, true, not, zero, pos, neg, lt, gt, le, ge]
It defaults to 'true' (=='ne', not-zero) if omitted. ('pos' will translate into 'pl', 'neg' into 'mi')
@todo signed: lts==neg?, gts==eq+pos?, les==neg+eq?, ges==pos?
The <expression> is optional. If it is provided, it will be evaluated first. Only the [true] and [not] and [zero]
if-statuses can be used when such a *comparison expression* is used. An example is:

53
tests/test_zp.py Normal file
View File

@ -0,0 +1,53 @@
import pytest
from il65.symbols import Zeropage, SymbolError, DataType
def test_zp_configure_onlyonce():
zp = Zeropage()
zp.configure()
with pytest.raises(SymbolError):
zp.configure()
def test_zp_names():
zp = Zeropage()
zp.configure()
zp.allocate("", DataType.BYTE)
zp.allocate("", DataType.BYTE)
zp.allocate("varname", DataType.BYTE)
with pytest.raises(AssertionError):
zp.allocate("varname", DataType.BYTE)
zp.allocate("varname2", DataType.BYTE)
def test_zp_noclobber_allocation():
zp = Zeropage()
zp.configure(False)
assert zp.available() == 9
with pytest.raises(LookupError):
zp.allocate("impossible", DataType.FLOAT) # in regular zp there aren't 5 sequential bytes free
for i in range(zp.available()):
zp.allocate("bytevar"+str(i), DataType.BYTE)
assert zp.available() == 0
with pytest.raises(LookupError):
zp.allocate("", DataType.BYTE)
with pytest.raises(LookupError):
zp.allocate("", DataType.WORD)
def test_zp_clobber_allocation():
zp = Zeropage()
zp.configure(True)
assert zp.available() == 248
loc = zp.allocate("", DataType.FLOAT)
assert loc > 3 and loc not in zp.free
num, rest = divmod(zp.available(), 5)
for _ in range(num):
zp.allocate("", DataType.FLOAT)
assert zp.available() == rest
for _ in range(rest // 2):
zp.allocate("", DataType.WORD)
assert zp.available() == 1
zp.allocate("last", DataType.BYTE)
with pytest.raises(LookupError):
zp.allocate("impossible", DataType.BYTE)

View File

@ -15,8 +15,15 @@ import "c64lib"
~ ZP $0004 {
var zpvar1
var zpvar2
memory zpmem1 = $f0
const zpconst = 1.234
var .word zpvarw1
var .word zpvarw2
var .float zpvarflt1 = 11.11
var .float zpvarflt2 = 22.22
var .float zpvarflt3
}
~ ZP {

View File

@ -1,11 +1,49 @@
output prg,basic
zp clobber, restore
;reg_preserve off ; @todo global option off/on default off?
~ main {
import "c64lib"
; zpvar myvar @todo allow for zp vars like this
~ ZP {
var .float fl1 = 3.1415927
var .float fl2 = 99.999999
var .float fl3 = 100000
}
~ main {
var .float fl1 = 3.1415927
var .float fl2 = 99.999999
var .float fl3 = 10
start
fl1 = 111111.22222
fl2 = 0
fl3 = 1
fl3 = -1
fl3 = 0.5
fl3 = -0.5
X=6
loop1
A=X
c64util.print_byte_hex(0, A)
c64.CHROUT!(" ")
X--
if_pos goto loop1
Y=6
loop2
A=Y
c64util.print_byte_hex(0, A)
c64.CHROUT!(" ")
Y--
if_neg goto stop
goto loop2
stop
return
}