var inits

This commit is contained in:
Irmen de Jong 2018-01-13 14:17:18 +01:00
parent 1df28c8091
commit 7218c17689
9 changed files with 213 additions and 116 deletions

1
.gitignore vendored
View File

@ -17,3 +17,4 @@
__pycache__/
parser.out
parsetab.py
!/il65/lib/*

View File

@ -16,7 +16,7 @@ from .plyparse import parse_file, ParseError, Module, Directive, Block, Subrouti
SymbolName, Dereference, AddressOf
from .plylex import SourceRef, print_bold
from .optimize import optimize
from .datatypes import DataType, datatype_sizes
from .datatypes import DataType, STRING_DATATYPES
class CompileError(Exception):
@ -86,7 +86,7 @@ class PlyParser:
zeropage = Zeropage(module.zp_options)
for vardef in zpnode.scope.filter_nodes(VarDef):
try:
vardef.zp_address = zeropage.allocate(vardef.name, vardef.datatype)
vardef.zp_address = zeropage.allocate(vardef)
except CompileError as x:
raise ParseError(str(x), vardef.sourceref)
@ -108,7 +108,6 @@ class PlyParser:
except Exception as x:
self.handle_internal_error(x, "process_expressions of node {} in block {}".format(node, block.name))
@no_type_check
def create_multiassigns(self, module: Module) -> None:
# create multi-assign statements from nested assignments (A=B=C=5),
# and optimize TargetRegisters down to single Register if it's just one register.
@ -120,7 +119,7 @@ class PlyParser:
return assign
for block, parent in module.all_scopes():
for node in block.nodes:
for node in block.nodes: # type: ignore
if isinstance(node, Assignment):
if isinstance(node.right, Assignment):
multi = reduce_right(node)
@ -428,8 +427,8 @@ class Zeropage:
assert self.SCRATCH_W1 not in self.free
assert self.SCRATCH_W2 not in self.free
def allocate(self, name: str, datatype: DataType) -> int:
assert not name or name not in {a[0] for a in self.allocations.values()}, "var name is not unique"
def allocate(self, vardef: VarDef) -> int:
assert not vardef.name or vardef.name not in {a[0] for a in self.allocations.values()}, "var name is not unique"
def sequential_free(location: int) -> bool:
return all(location + i in self.free for i in range(size))
@ -440,10 +439,30 @@ class Zeropage:
def make_allocation(location: int) -> int:
for loc in range(location, location + size):
self.free.remove(loc)
self.allocations[location] = (name or "<unnamed>", datatype)
self.allocations[location] = (vardef.name or "<unnamed>", vardef.datatype)
return location
size = datatype_sizes[datatype]
if vardef.datatype == DataType.BYTE:
size = 1
elif vardef.datatype == DataType.WORD:
size = 2
elif vardef.datatype == DataType.FLOAT:
print_bold("warning: {}: allocating a large datatype in zeropage".format(vardef.sourceref))
size = 5
elif vardef.datatype == DataType.BYTEARRAY:
print_bold("warning: {}: allocating a large datatype in zeropage".format(vardef.sourceref))
size = vardef.size[0]
elif vardef.datatype == DataType.WORDARRAY:
print_bold("warning: {}: allocating a large datatype in zeropage".format(vardef.sourceref))
size = vardef.size[0] * 2
elif vardef.datatype == DataType.MATRIX:
print_bold("warning: {}: allocating a large datatype in zeropage".format(vardef.sourceref))
size = vardef.size[0] * vardef.size[1]
elif vardef.datatype in STRING_DATATYPES:
print_bold("warning: {}: allocating a large datatype in zeropage".format(vardef.sourceref))
size = vardef.size[0]
else:
raise CompileError("cannot put datatype {:s} in ZP".format(vardef.datatype.name))
if len(self.free) > 0:
if size == 1:
for candidate in range(min(self.free), max(self.free)+1):

View File

@ -47,13 +47,6 @@ class DataType(enum.Enum):
return NotImplemented
datatype_sizes = {
DataType.BYTE: 1,
DataType.WORD: 2,
DataType.FLOAT: 5
}
STRING_DATATYPES = {DataType.STRING, DataType.STRING_P, DataType.STRING_S, DataType.STRING_PS}
REGISTER_SYMBOLS = {"A", "X", "Y", "AX", "AY", "XY", "SC", "SI"}

View File

@ -5,6 +5,7 @@ This is the assembly code generator (from the parse tree)
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
"""
import os
import re
import subprocess
import datetime
@ -13,7 +14,7 @@ from typing import Dict, TextIO, List, Any
from .plylex import print_bold
from .plyparse import Module, ProgramFormat, Block, Directive, VarDef, Label, Subroutine, AstNode, ZpOptions, \
InlineAssembly, Return, Register, LiteralValue
from .datatypes import VarType, DataType, datatype_sizes, to_hex, mflpt5_to_float, to_mflpt5, STRING_DATATYPES
from .datatypes import VarType, DataType, to_hex, to_mflpt5, STRING_DATATYPES
class CodeError(Exception):
@ -89,9 +90,9 @@ class AssemblyGenerator:
self.p("* = " + to_hex(self.module.address))
year = datetime.datetime.now().year
self.p("\v.word (+), {:d}".format(year))
self.p("\v.null $9e, format(' %d ', _il65_sysaddr), $3a, $8f, ' il65 by idj'")
self.p("\v.null $9e, format(' %d ', _il65_entrypoint), $3a, $8f, ' il65 by idj'")
self.p("+\v.word 0")
self.p("_il65_sysaddr\v; assembly code starts here\n")
self.p("_il65_entrypoint\v; assembly code starts here\n")
else:
self.p("; ---- program without sys call ----")
self.p("* = " + to_hex(self.module.address) + "\n")
@ -101,7 +102,7 @@ class AssemblyGenerator:
def init_vars_and_start(self) -> None:
if self.module.zp_options == ZpOptions.CLOBBER_RESTORE:
self.p("\vjsr il65_lib_zp.save_zeropage")
self.p("\vjsr _il65_save_zeropage")
self.p("\v; initialize all blocks (reset vars)")
if self.module.zeropage():
self.p("\vjsr ZP._il65_init_block")
@ -112,7 +113,12 @@ class AssemblyGenerator:
if self.module.zp_options == ZpOptions.CLOBBER_RESTORE:
self.p("\vjsr {:s}.start".format(self.module.main().label))
self.p("\vcld")
self.p("\vjmp il65_lib_zp.restore_zeropage")
self.p("\vjmp _il65_restore_zeropage\n")
# include the assembly code for the save/restore zeropage routines
zprestorefile = os.path.join(os.path.split(__file__)[0], "lib", "restorezp.asm")
with open(zprestorefile, "rU") as f:
for line in f.readlines():
self.p(line.rstrip("\n"))
else:
self.p("\vjmp {:s}.start".format(self.module.main().label))
self.p("")
@ -215,41 +221,61 @@ class AssemblyGenerator:
def generate_block_init(self, block: Block) -> None:
# generate the block initializer
# @todo add a block initializer subroutine that can contain custom reset/init code? (static initializer)
def _memset(varname: str, value: int, size: int) -> None:
value = value or 0
self.p("\vlda #<" + varname)
self.p("\vsta il65_lib.SCRATCH_ZPWORD1")
self.p("\vlda #>" + varname)
self.p("\vsta il65_lib.SCRATCH_ZPWORD1+1")
self.p("\vlda #" + to_hex(value))
self.p("\vldx #" + to_hex(size))
self.p("\vjsr il65_lib.memset")
def _memsetw(varname: str, value: int, size: int) -> None:
value = value or 0
self.p("\vlda #<" + varname)
self.p("\vsta il65_lib.SCRATCH_ZPWORD1")
self.p("\vlda #>" + varname)
self.p("\vsta il65_lib.SCRATCH_ZPWORD1+1")
self.p("\vlda #<" + to_hex(value))
self.p("\vldy #>" + to_hex(value))
self.p("\vldx #" + to_hex(size))
self.p("\vjsr il65_lib.memsetw")
self.p("_il65_init_block\v; (re)set vars to initial values")
self.p("\vlda #0\n\vldx #0")
float_inits = {}
string_inits = []
prev_value = 0
vardefs = [vd for vd in block.scope.filter_nodes(VarDef) if vd.vartype == VarType.VAR]
# @todo optimize init order (sort on value first to avoid needless register loads, etc)
for variable in vardefs:
vname = variable.name
vvalue = variable.value
if variable.datatype == DataType.BYTE:
if vvalue != prev_value:
self.p("\vlda #${:02x}".format(vvalue))
prev_value = vvalue
self.p("\vsta {:s}".format(vname))
elif variable.datatype == DataType.WORD:
if vvalue != prev_value:
self.p("\vlda #<${:04x}".format(vvalue))
self.p("\vldx #>${:04x}".format(vvalue))
prev_value = vvalue
self.p("\vsta {:s}".format(vname))
self.p("\vstx {:s}+1".format(vname))
elif variable.datatype == DataType.FLOAT:
fpbytes = to_mflpt5(vvalue) # type: ignore
float_inits[variable.name] = (vname, fpbytes, vvalue)
elif variable.datatype in STRING_DATATYPES:
string_inits.append(variable)
elif variable.datatype == DataType.BYTEARRAY:
pass # @todo init bytearray
elif variable.datatype == DataType.WORDARRAY:
pass # @todo init wordarray
elif variable.datatype == DataType.MATRIX:
pass # @todo init matrix
else:
raise CodeError("weird var datatype", variable.datatype)
prev_value_a, prev_value_x = None, None
vars_by_datatype = defaultdict(list) # type: Dict[DataType, List[VarDef]]
for vardef in block.scope.filter_nodes(VarDef):
if vardef.vartype == VarType.VAR:
vars_by_datatype[vardef.datatype].append(vardef)
for bytevar in sorted(vars_by_datatype[DataType.BYTE], key=lambda vd: vd.value):
if bytevar.value != prev_value_a:
self.p("\vlda #${:02x}".format(bytevar.value))
prev_value_a = bytevar.value
self.p("\vsta {:s}".format(bytevar.name))
for wordvar in sorted(vars_by_datatype[DataType.WORD], key=lambda vd: vd.value):
v_hi, v_lo = divmod(wordvar.value, 256)
if v_hi != prev_value_a:
self.p("\vlda #${:02x}".format(v_hi))
prev_value_a = v_hi
if v_lo != prev_value_x:
self.p("\vldx #${:02x}".format(v_lo))
prev_value_x = v_lo
self.p("\vsta {:s}".format(wordvar.name))
self.p("\vstx {:s}+1".format(wordvar.name))
for floatvar in vars_by_datatype[DataType.FLOAT]:
fpbytes = to_mflpt5(floatvar.value) # type: ignore
float_inits[floatvar.name] = (floatvar.name, fpbytes, floatvar.value)
for arrayvar in vars_by_datatype[DataType.BYTEARRAY]:
_memset(arrayvar.name, arrayvar.value, arrayvar.size[0])
for arrayvar in vars_by_datatype[DataType.WORDARRAY]:
_memsetw(arrayvar.name, arrayvar.value, arrayvar.size[0])
for arrayvar in vars_by_datatype[DataType.MATRIX]:
_memset(arrayvar.name, arrayvar.value, arrayvar.size[0] * arrayvar.size[1])
# @todo string datatype inits with 1 memcopy
if float_inits:
self.p("\vldx #4")
self.p("-")
@ -258,8 +284,6 @@ class AssemblyGenerator:
self.p("\vsta {:s},x".format(vname))
self.p("\vdex")
self.p("\vbpl -")
if string_inits:
pass # @todo init string block (1 memcopy)
self.p("\vrts\n")
for varname, (vname, fpbytes, fpvalue) in sorted(float_inits.items()):
self.p("_init_float_{:s}\t\t.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}\t; {}".format(varname, *fpbytes, fpvalue))
@ -324,8 +348,12 @@ class AssemblyGenerator:
# zeropage uses the zp_address we've allocated, instead of allocating memory here
for vardef in vars_by_vartype.get(VarType.VAR, []):
assert vardef.zp_address is not None
self.p("\v{:s} = {:s}\t; {:s} ({:d})".format(vardef.name, to_hex(vardef.zp_address),
vardef.datatype.name.lower(), datatype_sizes[vardef.datatype]))
if vardef.datatype in (DataType.WORDARRAY, DataType.BYTEARRAY, DataType.MATRIX):
size_str = "size " + str(vardef.size)
else:
size_str = ""
self.p("\v{:s} = {:s}\t; {:s} {:s}".format(vardef.name, to_hex(vardef.zp_address),
vardef.datatype.name.lower(), size_str))
else:
# create definitions for the variables that takes up empty space and will be initialized at startup
string_vars = []

View File

@ -1,58 +1,10 @@
; IL65 internal library routines
; IL65 internal library routines - always included by the compiler
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
; ;
;
; indent format: TABS, size=8
~ il65_lib_zp {
; note: separate block so the 64tass assembler can remove this when no zp restore is required
%asm {
; ---- store the Zeropage in a backup area
save_zeropage
sei
ldx #2
- lda $00,x
sta zp_backup,x
inx
bne -
cli
rts
restore_zeropage
php
pha
txa
pha
sei
lda $a0 ; save the current jiffy clock
sta zp_backup+$a0
lda $a1
sta zp_backup+$a1
lda $a2
sta zp_backup+$a2
ldx #2
- lda zp_backup-2,x
sta $00,x
inx
bne -
cli
pla
tax
pla
plp
rts
zp_backup .fill 256, 0
}
}
~ il65_lib {
; note: the following ZP scratch registers must be the same as in c64lib
memory .byte SCRATCH_ZP1 = $02 ; scratch register #1 in ZP
@ -120,7 +72,7 @@ memcopy16_up
rts ; done
; copy memory UP from (SCRATCH_ZPWORD1) to (AY) with length X (0 = 256)
; copy memory UP from (SCRATCH_ZPWORD1) to (AY) with length X (1 to 256, 0 meaning 256)
; destination must not overlap, or be before start, then overlap is possible.
; clobbers A, X, Y
@ -128,12 +80,37 @@ memcopy
sta SCRATCH_ZPWORD2
sty SCRATCH_ZPWORD2+1
ldy #0
- lda (SCRATCH_ZPWORD1),y
sta (SCRATCH_ZPWORD2),y
- lda (SCRATCH_ZPWORD1), y
sta (SCRATCH_ZPWORD2), y
iny
dex
bne -
rts
; fill memory from (SCRATCH_ZPWORD1) length X (1-256, 0=256) with value in A.
; clobbers X, Y
memset ldy #0
- sta (SCRATCH_ZPWORD1), y
iny
dex
bne -
rts
; fill memory from (SCRATCH_ZPWORD1) length X (1-256, 0=256) with word value in AY.
; clobbers A, X, Y
memsetw sty _mod_hi+1 ; self-modify
sta _mod_lo+1 ; self-modify
ldy #0
_mod_lo lda #$00 ; self-modified
sta (SCRATCH_ZPWORD1), y
iny
_mod_hi lda #$00 ; self-modified
sta (SCRATCH_ZPWORD1), y
iny
dex
bne _mod_lo
rts
}
}

43
il65/lib/restorezp.asm Normal file
View File

@ -0,0 +1,43 @@
; backup/restore the zero page
; this is in a separate file so it can be omitted completely if it's not needed.
_il65_save_zeropage
lda #%00101111
sta _il65_zp_backup ; default value for $00
lda #%00100111
sta _il65_zp_backup+1 ; default value for $01
ldx #2
- lda $00,x
sta _il65_zp_backup,x
inx
bne -
rts
_il65_restore_zeropage
php
pha
txa
pha
sei
lda $a0 ; save the current jiffy clock
sta _il65_zp_backup+$a0
lda $a1
sta _il65_zp_backup+$a1
lda $a2
sta _il65_zp_backup+$a2
ldx #0
- lda _il65_zp_backup,x
sta $00,x
inx
bne -
cli
pla
tax
pla
plp
rts
_il65_zp_backup
.fill 256

View File

@ -402,7 +402,7 @@ class InlineAssembly(AstNode):
assembly = attr.ib(type=str)
@attr.s(cmp=False, repr=False, slots=True)
@attr.s(cmp=False, repr=True, slots=True)
class VarDef(AstNode):
name = attr.ib(type=str)
vartype = attr.ib()
@ -430,6 +430,8 @@ class VarDef(AstNode):
assert self.size is None
self.size = self.datatype.dimensions or [1]
self.datatype = self.datatype.to_enum()
if self.datatype in {DataType.BYTEARRAY, DataType.WORDARRAY, DataType.MATRIX} and sum(self.size) in (0, 1):
print("warning: {}: array/matrix with size 1, use normal byte/word instead for efficiency".format(self.sourceref))
if self.vartype == VarType.CONST and self.value is None:
raise ParseError("constant value assignment is missing",
attr.evolve(self.sourceref, column=self.sourceref.column+len(self.name)))
@ -446,7 +448,9 @@ class VarDef(AstNode):
if self.vartype in (VarType.CONST, VarType.VAR):
try:
_, self.value = coerce_value(self.datatype, self.value, self.sourceref)
except (TypeError, OverflowError) as x:
except OverflowError as x:
raise ParseError(str(x), self.sourceref) from None
except TypeError as x:
raise ParseError("processed expression vor vardef is not a constant value: " + str(x), self.sourceref) from None
@ -679,11 +683,11 @@ def process_constant_expression(expr: Any, sourceref: SourceRef, symbolscope: Sc
else:
raise ExpressionEvaluationError("can only use math- or builtin function", expr.sourceref)
elif isinstance(target, Dereference): # '[...](1,2,3)'
return None # XXX
raise NotImplementedError("dereferenced call") # XXX
elif isinstance(target, int): # '64738()'
return None # XXX
raise NotImplementedError("immediate address call") # XXX
else:
raise NotImplementedError("weird call target", target) # XXX
raise NotImplementedError("weird call target", target)
else:
raise ParseError("function name required, not {}".format(expr.target.__class__.__name__), expr.sourceref)
elif not isinstance(expr, Expression):

View File

@ -116,7 +116,8 @@ The normal IRQ routine in the C-64's kernal will read and write several location
These locations will not be used by the compiler for zero page variables, so your variables will
not interfere with the IRQ routine and vice versa. This is true for the normal zp mode but also
for the mode where the whole zp has been taken over.
for the mode where the whole zp has been taken over. So the normal IRQ vector is still
running when your program is entered, even when you use ``%zp clobber``.
@todo: some global way (in ZP block) to promote certian other blocks/variables from that block or even

View File

@ -1,4 +1,5 @@
%output basic
%zp clobber,restore
~ ZP {
var zp1_1 = 200
@ -15,6 +16,17 @@
var zp2_2 = 100
var .word zp2_3 = $55aa
var .word zp2_4 = $66bb
var .word zp2_5 = $66bc
var .word zp2_6 = $66bd
var .word zp2_7 = $66be
var .word zp2_8 = $67be
var .word zp2_9 = $68be
var .word zp2_10 = $69be
var .word zp2_11 = $69be
var .array(4) array1
var .wordarray(4) warray1
var .matrix(3,3) matrix1
var .text string = "bye"
const .text zpc2_1 = "hello"
const zpc2_2 = 0
}
@ -22,12 +34,31 @@
~ main {
var .text hello_str = "hello there"
var .float float1 = 3.14
var .float float2 = 3.14
var .float float3 = 3.14
var .float float4 = 3.14
var .float float5 = 3.14
var .array(10) array1
var .wordarray(10) warray1
var .matrix(4,4) matrix1
var b1 = 10
var b2 = 20
var b3 = 10
var b4 = 20
var b5 = 10
var b6 = 20
var b7 = 10
var b8 = 30
var b9 = 30
var b10 = 40
var b11 = 40
var b12 = 30
var b13 = 40
var b14 = 0
var b15 = 0
var b16 = 0
start: