mirror of
https://github.com/irmen/prog8.git
synced 2025-01-11 13:29:45 +00:00
allow floats in ZP, if_pos and if_neg added, ZP allocations more flexible
This commit is contained in:
parent
e67e4c0b13
commit
a5283bfc7b
@ -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))
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
10
reference.md
10
reference.md
@ -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
53
tests/test_zp.py
Normal 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)
|
@ -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 {
|
||||
|
42
todo.ill
42
todo.ill
@ -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
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user