register preserve is no longer the default

This commit is contained in:
Irmen de Jong 2018-01-01 23:24:39 +01:00
parent 5bfca554a4
commit 8a99844140
21 changed files with 703 additions and 726 deletions

View File

@ -19,7 +19,7 @@ which aims to provide many conveniences over raw assembly code (even when using
- automatic variable allocations, automatic string variables and string sharing
- automatic type conversions
- floating point operations
- automatically preserving and restoring CPU registers state, when calling routines that otherwise would clobber these
- optional automatic preserving and restoring CPU registers state, when calling routines that otherwise would clobber these
- abstracting away low level aspects such as zero page handling, program startup, explicit memory addresses
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
- source code labels automatically loaded in Vice emulator so it can show them in disassembly

View File

@ -27,12 +27,13 @@ class _AstNode:
class Block(_AstNode):
_unnamed_block_labels = {} # type: Dict[Block, str]
def __init__(self, name: str, sourceref: SourceRef, parent_scope: SymbolTable) -> None:
def __init__(self, name: str, sourceref: SourceRef, parent_scope: SymbolTable, preserve_registers: bool=False) -> None:
super().__init__(sourceref)
self.address = 0
self.name = name
self.statements = [] # type: List[_AstNode]
self.symbols = SymbolTable(name, parent_scope, self)
self.preserve_registers = preserve_registers
@property
def ignore(self) -> bool:
@ -484,7 +485,7 @@ class CallStmt(_AstNode):
def __init__(self, sourceref: SourceRef, target: Optional[Value] = None, *,
address: Optional[int] = None, arguments: List[Tuple[str, Any]] = None,
outputs: List[Tuple[str, Value]] = None, is_goto: bool = False,
preserve_regs: bool = True, condition: IfCondition = None) -> None:
preserve_regs: Set[str] = None, condition: IfCondition = None) -> None:
if not is_goto:
assert condition is None
super().__init__(sourceref)

View File

@ -417,12 +417,11 @@ class CodeGenerator:
self.p("\t\tin{:s}".format(reg.lower()))
else:
# x/y += 2..255
self.p("\t\tpha")
self.p("\t\tt{:s}a".format(reg.lower()))
self.p("\t\tclc")
self.p("\t\tadc #" + value_str)
self.p("\t\tta{:s}".format(reg.lower()))
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\tt{:s}a".format(reg.lower()))
self.p("\t\tclc")
self.p("\t\tadc #" + value_str)
self.p("\t\tta{:s}".format(reg.lower()))
elif reg == "AX":
# AX += 1..255
self.p("\t\tclc")
@ -446,14 +445,14 @@ class CodeGenerator:
self.p("+")
else:
# XY += 2..255
self.p("\t\tpha")
self.p("\t\ttxa")
self.p("\t\tclc")
self.p("\t\tadc #" + value_str)
self.p("\t\ttax")
self.p("\t\tbcc +")
self.p("\t\tiny")
self.p("+\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tclc")
self.p("\t\tadc #" + value_str)
self.p("\t\ttax")
self.p("\t\tbcc +")
self.p("\t\tiny")
self.p("+")
else:
raise CodeError("invalid incr register: " + reg)
else:
@ -467,12 +466,11 @@ class CodeGenerator:
self.p("\t\tde{:s}".format(reg.lower()))
else:
# x/y -= 2..255
self.p("\t\tpha")
self.p("\t\tt{:s}a".format(reg.lower()))
self.p("\t\tsec")
self.p("\t\tsbc #" + value_str)
self.p("\t\tta{:s}".format(reg.lower()))
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\tt{:s}a".format(reg.lower()))
self.p("\t\tsec")
self.p("\t\tsbc #" + value_str)
self.p("\t\tta{:s}".format(reg.lower()))
elif reg == "AX":
# AX -= 1..255
self.p("\t\tsec")
@ -496,14 +494,14 @@ class CodeGenerator:
self.p("+\t\tdex")
else:
# XY -= 2..255
self.p("\t\tpha")
self.p("\t\ttxa")
self.p("\t\tsec")
self.p("\t\tsbc #" + value_str)
self.p("\t\ttax")
self.p("\t\tbcs +")
self.p("\t\tdey")
self.p("+\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tsec")
self.p("\t\tsbc #" + value_str)
self.p("\t\ttax")
self.p("\t\tbcs +")
self.p("\t\tdey")
self.p("+")
else:
raise CodeError("invalid decr register: " + reg)
elif isinstance(stmt.what, (MemMappedValue, IndirectValue)):
@ -519,16 +517,15 @@ class CodeGenerator:
if howmuch == 1:
self.p("\t\t{:s} {:s}".format("inc" if is_incr else "dec", what_str))
else:
self.p("\t\tpha")
self.p("\t\tlda " + what_str)
if is_incr:
self.p("\t\tclc")
self.p("\t\tadc #" + value_str)
else:
self.p("\t\tsec")
self.p("\t\tsbc #" + value_str)
self.p("\t\tsta " + what_str)
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\tlda " + what_str)
if is_incr:
self.p("\t\tclc")
self.p("\t\tadc #" + value_str)
else:
self.p("\t\tsec")
self.p("\t\tsbc #" + value_str)
self.p("\t\tsta " + what_str)
elif what.datatype == DataType.WORD:
if howmuch == 1:
# mem.word +=/-= 1
@ -538,32 +535,31 @@ class CodeGenerator:
self.p("\t\tinc {:s}+1".format(what_str))
self.p("+")
else:
self.p("\t\tpha")
self.p("\t\tlda " + what_str)
self.p("\t\tbne +")
self.p("\t\tdec {:s}+1".format(what_str))
self.p("+\t\tdec " + what_str)
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\tlda " + what_str)
self.p("\t\tbne +")
self.p("\t\tdec {:s}+1".format(what_str))
self.p("+\t\tdec " + what_str)
else:
# mem.word +=/-= 2..255
if is_incr:
self.p("\t\tpha")
self.p("\t\tclc")
self.p("\t\tlda " + what_str)
self.p("\t\tadc #" + value_str)
self.p("\t\tsta " + what_str)
self.p("\t\tbcc +")
self.p("\t\tinc {:s}+1".format(what_str))
self.p("+\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\tclc")
self.p("\t\tlda " + what_str)
self.p("\t\tadc #" + value_str)
self.p("\t\tsta " + what_str)
self.p("\t\tbcc +")
self.p("\t\tinc {:s}+1".format(what_str))
self.p("+")
else:
self.p("\t\tpha")
self.p("\t\tsec")
self.p("\t\tlda " + what_str)
self.p("\t\tsbc #" + value_str)
self.p("\t\tsta " + what_str)
self.p("\t\tbcs +")
self.p("\t\tdec {:s}+1".format(what_str))
self.p("+\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\tsec")
self.p("\t\tlda " + what_str)
self.p("\t\tsbc #" + value_str)
self.p("\t\tsta " + what_str)
self.p("\t\tbcs +")
self.p("\t\tdec {:s}+1".format(what_str))
self.p("+")
elif what.datatype == DataType.FLOAT:
if howmuch == 1.0:
# special case for +/-1
@ -914,15 +910,17 @@ class CodeGenerator:
return True
return False
def unclobber_result_registers(registers: Set[str], output_assignments: List[AssignmentStmt]) -> None:
def unclobber_result_registers(registers: Set[str], output_assignments: List[AssignmentStmt]) -> Set[str]:
result = registers.copy()
for a in output_assignments:
for lv in a.leftvalues:
if isinstance(lv, RegisterValue):
if len(lv.register) == 1:
registers.discard(lv.register)
result.discard(lv.register)
else:
for r in lv.register:
registers.discard(r)
result.discard(r)
return result
if stmt.target.name:
symblock, targetdef = self.cur_block.lookup(stmt.target.name)
@ -941,10 +939,10 @@ class CodeGenerator:
return
clobbered = set() # type: Set[str]
if targetdef.clobbered_registers:
if stmt.preserve_regs:
clobbered = targetdef.clobbered_registers
unclobber_result_registers(clobbered, stmt.desugared_output_assignments)
with self.preserving_registers(clobbered, loads_a_within=params_load_a()):
if stmt.preserve_regs is not None:
clobbered = targetdef.clobbered_registers & stmt.preserve_regs
clobbered = unclobber_result_registers(clobbered, stmt.desugared_output_assignments)
with self.preserving_registers(clobbered, loads_a_within=params_load_a(), always_preserve=stmt.preserve_regs is not None):
generate_param_assignments()
branch_emitter(targetstr, False, False)
generate_result_assignments()
@ -974,14 +972,14 @@ class CodeGenerator:
# no result assignments because it's a goto
else:
# indirect call to subroutine
preserve_regs = {'A', 'X', 'Y'} if stmt.preserve_regs else set()
unclobber_result_registers(preserve_regs, stmt.desugared_output_assignments)
with self.preserving_registers(preserve_regs, loads_a_within=params_load_a()):
preserve_regs = unclobber_result_registers(stmt.preserve_regs or set(), stmt.desugared_output_assignments)
with self.preserving_registers(preserve_regs, loads_a_within=params_load_a(),
always_preserve=stmt.preserve_regs is not None):
generate_param_assignments()
if targetstr in REGISTER_WORDS:
print("warning: {}: indirect register pair call is quite inefficient, use a jump table in memory instead?"
.format(stmt.sourceref))
if stmt.preserve_regs:
if stmt.preserve_regs is not None:
# cannot use zp scratch because it may be used by the register backup. This is very inefficient code!
self.p("\t\tjsr il65_lib.jsr_indirect_nozpuse_"+targetstr)
@ -1009,9 +1007,9 @@ class CodeGenerator:
branch_emitter(targetstr, True, False)
# no result assignments because it's a goto
else:
preserve_regs = {'A', 'X', 'Y'} if stmt.preserve_regs else set()
unclobber_result_registers(preserve_regs, stmt.desugared_output_assignments)
with self.preserving_registers(preserve_regs, loads_a_within=params_load_a()):
preserve_regs = unclobber_result_registers(stmt.preserve_regs or set(), stmt.desugared_output_assignments)
with self.preserving_registers(preserve_regs, loads_a_within=params_load_a(),
always_preserve=stmt.preserve_regs is not None):
generate_param_assignments()
branch_emitter(targetstr, False, False)
generate_result_assignments()
@ -1040,19 +1038,17 @@ class CodeGenerator:
self.p("\t\tclc")
self.p("\t\tadc " + r_str)
elif lvalue.register == "X":
self.p("\t\tpha")
self.p("\t\ttxa")
self.p("\t\tclc")
self.p("\t\tadc " + r_str)
self.p("\t\ttax")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tclc")
self.p("\t\tadc " + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tpha")
self.p("\t\ttya")
self.p("\t\tclc")
self.p("\t\tadc " + r_str)
self.p("\t\ttay")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tclc")
self.p("\t\tadc " + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo +=.word
elif operator == "-=":
@ -1060,70 +1056,62 @@ class CodeGenerator:
self.p("\t\tsec")
self.p("\t\tsbc " + r_str)
elif lvalue.register == "X":
self.p("\t\tpha")
self.p("\t\ttxa")
self.p("\t\tsec")
self.p("\t\tsbc " + r_str)
self.p("\t\ttax")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tsec")
self.p("\t\tsbc " + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tpha")
self.p("\t\ttya")
self.p("\t\tsec")
self.p("\t\tsbc " + r_str)
self.p("\t\ttay")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tsec")
self.p("\t\tsbc " + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo -=.word
elif operator == "&=":
if lvalue.register == "A":
self.p("\t\tand " + r_str)
elif lvalue.register == "X":
self.p("\t\tpha")
self.p("\t\ttxa")
self.p("\t\tand " + r_str)
self.p("\t\ttax")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tand " + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tpha")
self.p("\t\ttya")
self.p("\t\tand " + r_str)
self.p("\t\ttay")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tand " + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo &=.word
elif operator == "|=":
if lvalue.register == "A":
self.p("\t\tora " + r_str)
elif lvalue.register == "X":
self.p("\t\tpha")
self.p("\t\ttxa")
self.p("\t\tora " + r_str)
self.p("\t\ttax")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tora " + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tpha")
self.p("\t\ttya")
self.p("\t\tora " + r_str)
self.p("\t\ttay")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tora " + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo |=.word
elif operator == "^=":
if lvalue.register == "A":
self.p("\t\teor " + r_str)
elif lvalue.register == "X":
self.p("\t\tpha")
self.p("\t\ttxa")
self.p("\t\teor " + r_str)
self.p("\t\ttax")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\teor " + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tpha")
self.p("\t\ttya")
self.p("\t\teor " + r_str)
self.p("\t\ttay")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\teor " + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo ^=.word
elif operator == ">>=":
@ -1133,17 +1121,15 @@ class CodeGenerator:
if lvalue.register == "A":
self.p("\t\tlsr " + r_str)
elif lvalue.register == "X":
self.p("\t\tpha")
self.p("\t\ttxa")
self.p("\t\tlsr " + r_str)
self.p("\t\ttax")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tlsr " + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tpha")
self.p("\t\ttya")
self.p("\t\tlsr " + r_str)
self.p("\t\ttay")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tlsr " + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported lvalue register for shift", str(lvalue)) # @todo >>=.word
elif operator == "<<=":
@ -1153,17 +1139,15 @@ class CodeGenerator:
if lvalue.register == "A":
self.p("\t\tasl " + r_str)
elif lvalue.register == "X":
self.p("\t\tpha")
self.p("\t\ttxa")
self.p("\t\tasl " + r_str)
self.p("\t\ttax")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tasl " + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tpha")
self.p("\t\ttya")
self.p("\t\tasl " + r_str)
self.p("\t\ttay")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tasl " + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported lvalue register for shift", str(lvalue)) # @todo >>=.word
@ -1174,19 +1158,17 @@ class CodeGenerator:
self.p("\t\tclc")
self.p("\t\tadc #" + r_str)
elif lvalue.register == "X":
self.p("\t\tpha")
self.p("\t\ttxa")
self.p("\t\tclc")
self.p("\t\tadc #" + r_str)
self.p("\t\ttax")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tclc")
self.p("\t\tadc #" + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tpha")
self.p("\t\ttya")
self.p("\t\tclc")
self.p("\t\tadc #" + r_str)
self.p("\t\ttay")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tclc")
self.p("\t\tadc #" + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo +=.word
elif operator == "-=":
@ -1194,70 +1176,62 @@ class CodeGenerator:
self.p("\t\tsec")
self.p("\t\tsbc #" + r_str)
elif lvalue.register == "X":
self.p("\t\tpha")
self.p("\t\ttxa")
self.p("\t\tsec")
self.p("\t\tsbc #" + r_str)
self.p("\t\ttax")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tsec")
self.p("\t\tsbc #" + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tpha")
self.p("\t\ttya")
self.p("\t\tsec")
self.p("\t\tsbc #" + r_str)
self.p("\t\ttay")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tsec")
self.p("\t\tsbc #" + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo -=.word
elif operator == "&=":
if lvalue.register == "A":
self.p("\t\tand #" + r_str)
elif lvalue.register == "X":
self.p("\t\tpha")
self.p("\t\ttxa")
self.p("\t\tand #" + r_str)
self.p("\t\ttax")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tand #" + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tpha")
self.p("\t\ttya")
self.p("\t\tand #" + r_str)
self.p("\t\ttay")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tand #" + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo &=.word
elif operator == "|=":
if lvalue.register == "A":
self.p("\t\tora #" + r_str)
elif lvalue.register == "X":
self.p("\t\tpha")
self.p("\t\ttxa")
self.p("\t\tora #" + r_str)
self.p("\t\ttax")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tora #" + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tpha")
self.p("\t\ttya")
self.p("\t\tora #" + r_str)
self.p("\t\ttay")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tora #" + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo |=.word
elif operator == "^=":
if lvalue.register == "A":
self.p("\t\teor #" + r_str)
elif lvalue.register == "X":
self.p("\t\tpha")
self.p("\t\ttxa")
self.p("\t\teor #" + r_str)
self.p("\t\ttax")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\teor #" + r_str)
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tpha")
self.p("\t\ttya")
self.p("\t\teor #" + r_str)
self.p("\t\ttay")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\teor #" + r_str)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo ^=.word
elif operator == ">>=":
@ -1271,17 +1245,15 @@ class CodeGenerator:
if lvalue.register == "A":
shifts_A(rvalue.value)
elif lvalue.register == "X":
self.p("\t\tpha")
self.p("\t\ttxa")
shifts_A(rvalue.value)
self.p("\t\ttax")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
shifts_A(rvalue.value)
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tpha")
self.p("\t\ttya")
shifts_A(rvalue.value)
self.p("\t\ttay")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttya")
shifts_A(rvalue.value)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo >>=.word
elif operator == "<<=":
@ -1295,17 +1267,15 @@ class CodeGenerator:
if lvalue.register == "A":
shifts_A(rvalue.value)
elif lvalue.register == "X":
self.p("\t\tpha")
self.p("\t\ttxa")
shifts_A(rvalue.value)
self.p("\t\ttax")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
shifts_A(rvalue.value)
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tpha")
self.p("\t\ttya")
shifts_A(rvalue.value)
self.p("\t\ttay")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttya")
shifts_A(rvalue.value)
self.p("\t\ttay")
else:
raise CodeError("unsupported register for aug assign", str(lvalue)) # @todo <<=.word
@ -1319,20 +1289,18 @@ class CodeGenerator:
self.p("\t\tadc " + Parser.to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tpha")
self.p("\t\ttxa")
self.p("\t\tclc")
self.p("\t\tadc " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tclc")
self.p("\t\tadc " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tpha")
self.p("\t\ttya")
self.p("\t\tclc")
self.p("\t\tadc " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tclc")
self.p("\t\tadc " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo +=.word
elif operator == "-=":
@ -1344,20 +1312,18 @@ class CodeGenerator:
self.p("\t\tsbc " + Parser.to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tpha")
self.p("\t\ttxa")
self.p("\t\tsec")
self.p("\t\tsbc " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tsec")
self.p("\t\tsbc " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tpha")
self.p("\t\ttya")
self.p("\t\tsec")
self.p("\t\tsbc " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tsec")
self.p("\t\tsbc " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo -=.word
elif operator == "&=":
@ -1368,18 +1334,16 @@ class CodeGenerator:
self.p("\t\tand " + Parser.to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tpha")
self.p("\t\ttxa")
self.p("\t\tand " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tand " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tpha")
self.p("\t\ttya")
self.p("\t\tand " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tand " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo &=.word
elif operator == "|=":
@ -1390,18 +1354,16 @@ class CodeGenerator:
self.p("\t\tora " + Parser.to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tpha")
self.p("\t\ttxa")
self.p("\t\tora " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tora " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tpha")
self.p("\t\ttya")
self.p("\t\tora " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tora " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo |=.word
elif operator == "^=":
@ -1412,18 +1374,16 @@ class CodeGenerator:
self.p("\t\teor " + Parser.to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tpha")
self.p("\t\ttxa")
self.p("\t\teor " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\teor " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tpha")
self.p("\t\ttya")
self.p("\t\teor " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\teor " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo ^=.word
elif operator == ">>=":
@ -1434,18 +1394,16 @@ class CodeGenerator:
self.p("\t\tlsr " + Parser.to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tpha")
self.p("\t\ttxa")
self.p("\t\tlsr " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tlsr " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tpha")
self.p("\t\ttya")
self.p("\t\tlsr " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tlsr " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo >>=.word
elif operator == "<<=":
@ -1456,18 +1414,16 @@ class CodeGenerator:
self.p("\t\tasl " + Parser.to_hex(Zeropage.SCRATCH_B1))
elif lvalue.register == "X":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tpha")
self.p("\t\ttxa")
self.p("\t\tasl " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttxa")
self.p("\t\tasl " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttax")
elif lvalue.register == "Y":
self.p("\t\tst{:s} {:s}".format(rvalue.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tpha")
self.p("\t\ttya")
self.p("\t\tasl " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
self.p("\t\tpla")
with self.preserving_registers({'A'}):
self.p("\t\ttya")
self.p("\t\tasl " + Parser.to_hex(Zeropage.SCRATCH_B1))
self.p("\t\ttay")
else:
raise CodeError("unsupported lvalue register for aug assign", str(lvalue)) # @todo <<=.word
@ -1556,14 +1512,14 @@ class CodeGenerator:
floatvalue = float(rvalue.value)
mflpt = self.to_mflpt5(floatvalue)
target = mmv.name or Parser.to_hex(mmv.address)
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")
with self.preserving_registers({'A'}):
self.p("\t\t\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))
def generate_assign_reg_to_memory(self, lv: MemMappedValue, r_register: str) -> None:
# Memory = Register
@ -1692,9 +1648,12 @@ class CodeGenerator:
raise CodeError("invalid register " + lv.register)
@contextlib.contextmanager
def preserving_registers(self, registers: Set[str], loads_a_within: bool=False):
# this clobbers a ZP scratch register and is therefore NOT safe to use in interrupts
def preserving_registers(self, registers: Set[str], loads_a_within: bool=False, always_preserve: bool=False):
# this sometimes clobbers a ZP scratch register and is therefore NOT safe to use in interrupts
# see http://6502.org/tutorials/register_preservation.html
if not self.cur_block.preserve_registers and not always_preserve:
yield
return
if registers == {'A'}:
self.p("\t\tpha")
yield

View File

@ -39,6 +39,7 @@ class ParseResult:
self.blocks = [] # type: List[Block]
self.subroutine_usage = defaultdict(set) # type: Dict[Tuple[str, str], Set[str]]
self.zeropage = Zeropage()
self.preserve_registers = False
def all_blocks(self) -> Generator[Block, None, None]:
for block in self.blocks:
@ -176,7 +177,7 @@ class Parser:
break
def _parse_1(self) -> None:
self.cur_block = Block("<header>", self.sourceref, self.root_scope)
self.cur_block = Block("<header>", self.sourceref, self.root_scope, self.result.preserve_registers)
self.result.add_block(self.cur_block)
self.parse_header()
if not self.parsing_import:
@ -188,7 +189,7 @@ class Parser:
block = self.parse_block()
if block:
self.result.add_block(block)
elif next_line.startswith(("import ", "import\t")):
elif next_line.startswith(("%import ", "%import\t")):
self.parse_import()
else:
break
@ -402,76 +403,91 @@ class Parser:
self.result.format = ProgramFormat.RAW
output_specified = False
zp_specified = False
preserve_specified = False
while True:
self._parse_comments()
line = self.next_line()
if line.startswith(("output ", "output\t")):
if output_specified:
raise self.PError("can only specify output options once")
output_specified = True
_, _, optionstr = line.partition(" ")
options = set(optionstr.replace(' ', '').split(','))
self.result.with_sys = False
self.result.format = ProgramFormat.RAW
if "raw" in options:
options.remove("raw")
if "prg" in options:
options.remove("prg")
self.result.format = ProgramFormat.PRG
if "basic" in options:
options.remove("basic")
if self.result.format == ProgramFormat.PRG:
self.result.with_sys = True
else:
raise self.PError("can only use basic output option with prg, not raw")
if options:
raise self.PError("invalid output option(s): " + str(options))
elif line.startswith(("zp ", "zp\t")):
if zp_specified:
raise self.PError("can only specify ZP options once")
zp_specified = True
_, _, optionstr = line.partition(" ")
options = set(optionstr.replace(' ', '').split(','))
self.result.clobberzp = False
self.result.restorezp = False
if "clobber" in options:
options.remove("clobber")
self.result.clobberzp = True
if "restore" in options:
options.remove("restore")
if self.result.clobberzp:
self.result.restorezp = True
else:
raise self.PError("can only use restore zp option if clobber zp is used as well")
if options:
raise self.PError("invalid zp option(s): " + str(options))
elif line.startswith("address"):
if self.result.start_address:
raise self.PError("multiple occurrences of 'address'")
_, _, arg = line.partition(" ")
try:
self.result.start_address = parse_expr_as_int(arg, None, None, self.sourceref)
except ParseError:
raise self.PError("invalid address")
if self.result.format == ProgramFormat.PRG and self.result.with_sys and self.result.start_address != 0x0801:
raise self.PError("cannot use non-default 'address' when output format includes basic SYS program")
else:
# header parsing finished!
self.prev_line()
if not self.result.start_address:
# set the proper default start address
if self.result.format == ProgramFormat.PRG:
self.result.start_address = 0x0801 # normal C-64 basic program start address
elif self.result.format == ProgramFormat.RAW:
self.result.start_address = 0xc000 # default start for raw assembly
if self.result.format == ProgramFormat.PRG and self.result.with_sys and self.result.start_address != 0x0801:
raise self.PError("cannot use non-default 'address' when output format includes basic SYS program")
return
if line.startswith('%'):
directive = line.split(maxsplit=1)[0][1:]
if directive == "output":
if output_specified:
raise self.PError("can only specify output options once")
output_specified = True
_, _, optionstr = line.partition(" ")
options = set(optionstr.replace(' ', '').split(','))
self.result.with_sys = False
self.result.format = ProgramFormat.RAW
if "raw" in options:
options.remove("raw")
if "prg" in options:
options.remove("prg")
self.result.format = ProgramFormat.PRG
if "basic" in options:
options.remove("basic")
if self.result.format == ProgramFormat.PRG:
self.result.with_sys = True
else:
raise self.PError("can only use basic output option with prg, not raw")
if options:
raise self.PError("invalid output option(s): " + str(options))
continue
elif directive == "zp":
if zp_specified:
raise self.PError("can only specify ZP options once")
zp_specified = True
_, _, optionstr = line.partition(" ")
options = set(optionstr.replace(' ', '').split(','))
self.result.clobberzp = False
self.result.restorezp = False
if "clobber" in options:
options.remove("clobber")
self.result.clobberzp = True
if "restore" in options:
options.remove("restore")
if self.result.clobberzp:
self.result.restorezp = True
else:
raise self.PError("can only use restore zp option if clobber zp is used as well")
if options:
raise self.PError("invalid zp option(s): " + str(options))
continue
elif directive == "address":
if self.result.start_address:
raise self.PError("multiple occurrences of 'address'")
_, _, arg = line.partition(" ")
try:
self.result.start_address = parse_expr_as_int(arg, None, None, self.sourceref)
except ParseError:
raise self.PError("invalid address")
if self.result.format == ProgramFormat.PRG and self.result.with_sys and self.result.start_address != 0x0801:
raise self.PError("cannot use non-default 'address' when output format includes basic SYS program")
continue
elif directive == "preserve_registers":
if preserve_specified:
raise self.PError("can only specify preserve_registers option once")
preserve_specified = True
_, _, optionstr = line.partition(" ")
self.result.preserve_registers = optionstr in ("", "true", "yes")
continue
elif directive == "import":
break # the first import directive actually is not part of the header anymore
else:
raise self.PError("invalid directive")
break # no more directives, header parsing finished!
self.prev_line()
if not self.result.start_address:
# set the proper default start address
if self.result.format == ProgramFormat.PRG:
self.result.start_address = 0x0801 # normal C-64 basic program start address
elif self.result.format == ProgramFormat.RAW:
self.result.start_address = 0xc000 # default start for raw assembly
if self.result.format == ProgramFormat.PRG and self.result.with_sys and self.result.start_address != 0x0801:
raise self.PError("cannot use non-default 'address' when output format includes basic SYS program")
def parse_import(self) -> None:
line = self.next_line()
line = line.lstrip()
if not line.startswith(("import ", "import\t")):
if not line.startswith(("%import ", "%import\t")):
raise self.PError("expected import")
try:
_, arg = line.split(maxsplit=1)
@ -528,7 +544,7 @@ class Parser:
raise self.PError("expected '~' (block)")
block_args = line[1:].split()
arg = ""
self.cur_block = Block("", self.sourceref.copy(), self.root_scope)
self.cur_block = Block("", self.sourceref.copy(), self.root_scope, self.result.preserve_registers)
is_zp_block = False
while block_args:
arg = block_args.pop(0)
@ -542,7 +558,7 @@ class Parser:
raise self.PError("duplicate block name '{:s}', original definition at {}".format(arg, orig.sourceref))
self.cur_block = orig # zero page block occurrences are merged
else:
self.cur_block = Block(arg, self.sourceref.copy(), self.root_scope)
self.cur_block = Block(arg, self.sourceref.copy(), self.root_scope, self.result.preserve_registers)
try:
self.root_scope.define_scope(self.cur_block.symbols, self.cur_block.sourceref)
except SymbolError as x:
@ -605,14 +621,34 @@ class Parser:
line = self.next_line()
unstripped_line = line
line = line.strip()
if line == "}":
if line.startswith('%'):
directive, _, optionstr = line.partition(" ")
directive = directive[1:]
self.cur_block.preserve_registers = optionstr in ("", "true", "yes")
if directive in ("asminclude", "asmbinary"):
if is_zp_block:
raise self.PError("ZP block cannot contain assembler directives")
self.cur_block.statements.append(self.parse_asminclude(line))
elif directive == "asm":
if is_zp_block:
raise self.PError("ZP block cannot contain code statements")
self.prev_line()
self.cur_block.statements.append(self.parse_asm())
elif directive == "breakpoint":
self.cur_block.statements.append(BreakpointStmt(self.sourceref))
self.print_warning("breakpoint defined")
elif directive == "preserve_registers":
self.result.preserve_registers = optionstr in ("", "true", "yes")
else:
raise self.PError("invalid directive")
elif line == "}":
if is_zp_block and any(b.name == "ZP" for b in self.result.blocks):
return False, None # we already have the ZP block
if self.cur_block.ignore:
self.print_warning("ignoring block without name and address", self.cur_block.sourceref)
return False, None
return False, self.cur_block
if line.startswith(("var ", "var\t")):
elif line.startswith(("var ", "var\t")):
self.parse_var_def(line)
elif line.startswith(("const ", "const\t")):
self.parse_const_def(line)
@ -622,19 +658,9 @@ class Parser:
if is_zp_block:
raise self.PError("ZP block cannot contain subroutines")
self.parse_subroutine_def(line)
elif line.startswith(("asminclude ", "asminclude\t", "asmbinary ", "asmbinary\t")):
if is_zp_block:
raise self.PError("ZP block cannot contain assembler directives")
self.cur_block.statements.append(self.parse_asminclude(line))
elif line.startswith(("asm ", "asm\t")):
if is_zp_block:
raise self.PError("ZP block cannot contain code statements")
self.prev_line()
self.cur_block.statements.append(self.parse_asm())
elif line == "breakpoint":
self.cur_block.statements.append(BreakpointStmt(self.sourceref))
self.print_warning("breakpoint defined")
elif unstripped_line.startswith((" ", "\t")):
if line.endswith("{"):
raise self.PError("invalid statement")
if is_zp_block:
raise self.PError("ZP block cannot contain code statements")
self.cur_block.statements.append(self.parse_statement(line))
@ -714,7 +740,8 @@ class Parser:
if code_decl:
address = None
# parse the subroutine code lines (until the closing '}')
subroutine_block = Block(self.cur_block.name + "." + name, self.sourceref, self.cur_block.symbols)
subroutine_block = Block(self.cur_block.name + "." + name, self.sourceref, self.cur_block.symbols,
self.cur_block.preserve_registers)
current_block = self.cur_block
self.cur_block = subroutine_block
while True:
@ -727,10 +754,12 @@ class Parser:
break
if line.startswith(("sub ", "sub\t")):
raise self.PError("cannot nest subroutines")
elif line.startswith(("asm ", "asm\t")):
elif line.startswith(("%asm ", "%asm\t")):
self.prev_line()
subroutine_block.statements.append(self.parse_asm())
elif unstripped_line.startswith((" ", "\t")):
if line.endswith("{"):
raise self.PError("invalid statement")
subroutine_block.statements.append(self.parse_statement(line))
elif line:
self.parse_label(line)
@ -792,25 +821,31 @@ class Parser:
# conditional goto
groups = match.groupdict()
subname = groups["subname"]
if '!' in subname:
raise self.PError("goto is always without register preservation, should not have exclamation mark")
if groups["if"] == "if" and not groups["cond"]:
raise self.PError("need explicit if status when a condition is not present")
condition = self.parse_if_condition(groups["if"], groups["cond"])
return self.parse_call_or_goto(subname, groups["arguments"], None, False, True, condition=condition)
return self.parse_call_or_goto(subname, groups["arguments"], None, None, True, condition=condition)
match = re.fullmatch(r"goto\s+(?P<subname>[\S]+?)\s*(\((?P<arguments>.*)\))?\s*", line)
if match:
# goto
groups = match.groupdict()
subname = groups["subname"]
if '!' in subname:
raise self.PError("goto is always without register preservation, should not have exclamation mark")
return self.parse_call_or_goto(subname, groups["arguments"], None, False, True)
match = re.fullmatch(r"(?P<outputs>[^\(]*\s*=)?\s*(?P<subname>[\S]+?)\s*(?P<fcall>[!]?)\s*(\((?P<arguments>.*)\))?\s*", line)
return self.parse_call_or_goto(subname, groups["arguments"], None, None, True)
match = re.fullmatch(r"(?P<outputs>[^\(]*\s*=)?\s*(?P<subname>[\S]+?)\s*(?P<preserve>!\s*[A-Z]*)?\s*(\((?P<arguments>.*)\))?\s*", line)
if match:
# subroutine call (not a goto) with possible output param assignment
groups = match.groupdict()
preserve = not bool(groups["fcall"])
preserve = None
preserve_str = groups["preserve"]
if preserve_str and preserve_str.startswith('!'):
preserve_str = preserve_str.replace(' ', '')
if preserve_str == "!":
preserve = {'A', 'X', 'Y'}
else:
preserve = set(preserve_str[1:])
for r in preserve:
if r not in REGISTER_BYTES:
raise self.PError("invalid register in call preservation list")
subname = groups["subname"]
arguments = groups["arguments"]
outputs = groups["outputs"] or ""
@ -843,7 +878,7 @@ class Parser:
raise self.PError("invalid statement")
def parse_call_or_goto(self, targetstr: str, argumentstr: str, outputstr: str,
preserve_regs=True, is_goto=False, condition: IfCondition=None) -> CallStmt:
preserve_regs: Set[str]=None, is_goto: bool=False, condition: IfCondition=None) -> CallStmt:
if not is_goto:
assert condition is None
argumentstr = argumentstr.strip() if argumentstr else ""
@ -1008,8 +1043,8 @@ class Parser:
def parse_asm(self) -> InlineAsm:
line = self.next_line()
aline = line.split()
if not len(aline) == 2 or aline[0] != "asm" or aline[1] != "{":
raise self.PError("invalid asm start")
if not len(aline) == 2 or aline[0] != "%asm" or aline[1] != "{":
raise self.PError("invalid asm directive")
asmlines = [] # type: List[str]
while True:
line = self.next_line()
@ -1036,7 +1071,7 @@ class Parser:
def parse_asminclude(self, line: str) -> InlineAsm:
aline = line.split()
if len(aline) < 2:
raise self.PError("invalid asminclude or asmbinary statement")
raise self.PError("invalid asminclude or asmbinary directive")
filename = aline[1]
if not filename.startswith('"') or not filename.endswith('"'):
raise self.PError("filename must be between quotes")
@ -1049,14 +1084,14 @@ class Parser:
raise self.PError("included file not found")
print("copying included file to output location:", filename)
shutil.copy(filename_in_sourcedir, filename_in_output_location)
if aline[0] == "asminclude":
if aline[0] == "%asminclude":
if len(aline) == 3:
scopename = aline[2]
lines = ['{:s}\t.binclude "{:s}"'.format(scopename, filename)]
else:
raise self.PError("invalid asminclude statement")
raise self.PError("invalid asminclude directive")
return InlineAsm(lines, self.sourceref)
elif aline[0] == "asmbinary":
elif aline[0] == "%asmbinary":
if len(aline) == 4:
offset = parse_expr_as_int(aline[2], None, None, self.sourceref)
length = parse_expr_as_int(aline[3], None, None, self.sourceref)
@ -1067,7 +1102,7 @@ class Parser:
elif len(aline) == 2:
lines = ['\t.binary "{:s}"'.format(filename)]
else:
raise self.PError("invalid asmbinary statement")
raise self.PError("invalid asmbinary directive")
return InlineAsm(lines, self.sourceref)
else:
raise self.PError("invalid statement")

View File

@ -234,7 +234,7 @@ sub init_system () -> (?) {
; This means that the BASIC, KERNAL and CHARGEN ROMs are banked in,
; the VIC, SID and CIA chips are reset, screen is cleared, and the default IRQ is set.
; Also a different color scheme is chosen to identify ourselves a little.
asm {
%asm {
sei
cld
lda #%00101111
@ -269,7 +269,7 @@ sub init_system () -> (?) {
sub FREADS32 () -> (?) {
; ---- fac1 = signed int32 from $62-$65 big endian (MSB FIRST)
asm {
%asm {
lda $62
eor #$ff
asl a
@ -281,7 +281,7 @@ sub FREADS32 () -> (?) {
sub FREADUS32 () -> (?) {
; ---- fac1 = uint32 from $62-$65 big endian (MSB FIRST)
asm {
%asm {
sec
lda #0
ldx #$a0
@ -292,7 +292,7 @@ sub FREADUS32 () -> (?) {
sub FREADS24AXY (lo: A, mid: X, hi: Y) -> (?) {
; ---- fac1 = signed int24 (A/X/Y contain lo/mid/hi bytes)
; note: there is no FREADU24AXY (unsigned), use FREADUS32 instead.
asm {
%asm {
sty $62
stx $63
sta $64
@ -308,7 +308,7 @@ sub FREADS24AXY (lo: A, mid: X, hi: Y) -> (?) {
sub GIVUAYF (uword: AY) -> (?) {
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
asm {
%asm {
sty $62
sta $63
ldx #$90
@ -319,7 +319,7 @@ sub GIVUAYF (uword: AY) -> (?) {
sub GIVAYFAY (sword: AY) -> (?) {
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
asm {
%asm {
sta c64.SCRATCH_ZP1
tya
ldy c64.SCRATCH_ZP1
@ -329,7 +329,7 @@ sub GIVAYFAY (sword: AY) -> (?) {
sub FTOSWRDAY () -> (AY, X?) {
; ---- fac1 to signed word in A/Y
asm {
%asm {
jsr c64.FTOSWORDYA ; note the inverse Y/A order
sta c64.SCRATCH_ZP1
tya
@ -340,7 +340,7 @@ sub FTOSWRDAY () -> (AY, X?) {
sub GETADRAY () -> (AY, X?) {
; ---- fac1 to unsigned word in A/Y
asm {
%asm {
jsr c64.GETADR ; this uses the inverse order, Y/A
sta c64.SCRATCH_ZP1
tya
@ -353,7 +353,7 @@ sub GETADRAY () -> (AY, X?) {
sub copy_mflt (source: XY) -> (A?, Y?) {
; ---- copy a 5 byte MFLT floating point variable to another place
; input: X/Y = source address, c64.SCRATCH_ZPWORD1 = destination address
asm {
%asm {
stx c64.SCRATCH_ZP1
sty c64.SCRATCH_ZPWORD1+1
ldy #0
@ -378,7 +378,7 @@ sub copy_mflt (source: XY) -> (A?, Y?) {
sub float_add_one (mflt: XY) -> (?) {
; ---- add 1 to the MFLT pointed to by X/Y. Clobbers A, X, Y
asm {
%asm {
stx c64.SCRATCH_ZP1
sty c64.SCRATCH_ZP2
txa
@ -394,7 +394,7 @@ sub float_add_one (mflt: XY) -> (?) {
sub float_sub_one (mflt: XY) -> (?) {
; ---- subtract 1 from the MFLT pointed to by X/Y. Clobbers A, X, Y
asm {
%asm {
stx c64.SCRATCH_ZP1
sty c64.SCRATCH_ZP2
lda #<c64.FL_FONE
@ -411,7 +411,7 @@ sub float_sub_one (mflt: XY) -> (?) {
sub float_add_SW1_to_XY (mflt: XY) -> (?) {
; ---- add MFLT pointed to by SCRATCH_ZPWORD1 to the MFLT pointed to by X/Y. Clobbers A, X, Y
asm {
%asm {
stx c64.SCRATCH_ZP1
sty c64.SCRATCH_ZP2
txa
@ -427,7 +427,7 @@ sub float_add_SW1_to_XY (mflt: XY) -> (?) {
sub float_sub_SW1_from_XY (mflt: XY) -> (?) {
; ---- subtract MFLT pointed to by SCRATCH_ZPWORD1 from the MFLT pointed to by X/Y. Clobbers A, X, Y
asm {
%asm {
stx c64.SCRATCH_ZP1
sty c64.SCRATCH_ZP2
lda c64.SCRATCH_ZPWORD1
@ -454,7 +454,7 @@ sub clear_screen (char:A, color: Y) -> () {
; ---- clear the character screen with the given fill character and character color.
; (assumes screen is at $0400, could be altered in the future with self-modifying code)
asm {
%asm {
sta _loop + 1 ; self-modifying
stx c64.SCRATCH_ZP1
ldx #0
@ -483,7 +483,7 @@ sub scroll_left_full (alsocolors: SC) -> (A?, X?, Y?) {
; ---- scroll the whole screen 1 character to the left
; contents of the rightmost column are unchanged, you should clear/refill this yourself
; Carry flag determines if screen color data should be scrolled too
asm {
%asm {
bcs +
jmp _scroll_screen
@ -542,7 +542,7 @@ sub scroll_right_full (alsocolors: SC) -> (A?, X?) {
; ---- scroll the whole screen 1 character to the right
; contents of the leftmost column are unchanged, you should clear/refill this yourself
; Carry flag determines if screen color data should be scrolled too
asm {
%asm {
bcs +
jmp _scroll_screen
@ -593,7 +593,7 @@ sub scroll_up_full (alsocolors: SC) -> (A?, X?) {
; ---- scroll the whole screen 1 character up
; contents of the bottom row are unchanged, you should refill/clear this yourself
; Carry flag determines if screen color data should be scrolled too
asm {
%asm {
bcs +
jmp _scroll_screen
@ -644,7 +644,7 @@ sub scroll_down_full (alsocolors: SC) -> (A?, X?) {
; ---- scroll the whole screen 1 character down
; contents of the top row are unchanged, you should refill/clear this yourself
; Carry flag determines if screen color data should be scrolled too
asm {
%asm {
bcs +
jmp _scroll_screen
@ -693,7 +693,7 @@ _scroll_screen
sub byte2decimal (ubyte: A) -> (Y, X, A) {
; ---- A to decimal string in Y/X/A (100s in Y, 10s in X, 1s in A)
asm {
%asm {
ldy #$2f
ldx #$3a
sec
@ -710,7 +710,7 @@ sub byte2decimal (ubyte: A) -> (Y, X, A) {
sub byte2hex (ubyte: A) -> (X, Y, A?) {
; ---- A to hex string in XY (first hex char in X, second hex char in Y)
asm {
%asm {
pha
and #$0f
tax
@ -733,7 +733,7 @@ hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as
var .text word2hex_output = "1234" ; 0-terminated, to make printing easier
sub word2hex (word: XY) -> (?) {
; ---- convert 16 bit word in X/Y into 4-character hexadecimal string into memory 'word2hex_output'
asm {
%asm {
stx c64.SCRATCH_ZP2
tya
jsr byte2hex
@ -757,7 +757,7 @@ sub word2bcd (word: XY) -> (A?, X?) {
; into a BCD value that is being doubled on each iteration. As all the
; arithmetic is being done in BCD the result is a binary to decimal
; conversion.
asm {
%asm {
stx c64.SCRATCH_ZP1
sty c64.SCRATCH_ZP2
sed ; switch to decimal mode
@ -789,7 +789,7 @@ sub word2bcd (word: XY) -> (A?, X?) {
var .array(5) word2decimal_output
sub word2decimal (word: XY) -> (?) {
; ---- convert 16 bit word in X/Y into decimal string into memory 'word2decimal_output'
asm {
%asm {
jsr word2bcd
lda word2bcd_bcdbuff+2
clc
@ -827,7 +827,7 @@ sub print_string (address: XY) -> (A?, Y?) {
; note: the IL65 compiler contains an optimization that will replace
; a call to this subroutine with a string argument of just one char,
; by just one call to c64.CHROUT of that single char.
asm {
%asm {
stx c64.SCRATCH_ZP1
sty c64.SCRATCH_ZP2
ldy #0
@ -843,7 +843,7 @@ sub print_string (address: XY) -> (A?, Y?) {
sub print_pstring (address: XY) -> (A?, X?, Y) {
; ---- print pstring (length as first byte) from X/Y, returns str len in Y
asm {
%asm {
stx c64.SCRATCH_ZP1
sty c64.SCRATCH_ZP2
ldy #0
@ -863,7 +863,7 @@ sub print_pstring (address: XY) -> (A?, X?, Y) {
sub print_pimmediate () -> () {
; ---- print pstring in memory immediately following the subroutine fast call instruction
; note that the clobbered registers (A,X,Y) are not listed ON PURPOSE
asm {
%asm {
tsx
lda $102,x
tay ; put high byte in y
@ -891,7 +891,7 @@ sub print_pimmediate () -> () {
sub print_byte_decimal0 (ubyte: A) -> (?) {
; ---- print the byte in A in decimal form, with left padding 0s (3 positions total)
asm {
%asm {
jsr byte2decimal
pha
tya
@ -906,7 +906,7 @@ sub print_byte_decimal0 (ubyte: A) -> (?) {
sub print_byte_decimal (ubyte: A) -> (?) {
; ---- print the byte in A in decimal form, without left padding 0s
asm {
%asm {
jsr byte2decimal
pha
cpy #'0'
@ -927,7 +927,7 @@ _print_tens txa
sub print_byte_hex (prefix: SC, ubyte: A) -> (?) {
; ---- print the byte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
asm {
%asm {
bcc +
pha
lda #'$'
@ -945,7 +945,7 @@ sub print_byte_hex (prefix: SC, ubyte: A) -> (?) {
sub print_word_hex (prefix: SC, word: XY) -> (?) {
; ---- print the (unsigned) word in X/Y in hexadecimal form (4 digits)
; (if Carry is set, a radix prefix '$' is printed as well)
asm {
%asm {
stx c64.SCRATCH_ZP1
tya
jsr print_byte_hex
@ -958,7 +958,7 @@ sub print_word_hex (prefix: SC, word: XY) -> (?) {
sub print_word_decimal0 (word: XY) -> (?) {
; ---- print the (unsigned) word in X/Y in decimal form, with left padding 0s (5 positions total)
asm {
%asm {
jsr word2decimal
lda word2decimal_output
jsr c64.CHROUT
@ -976,7 +976,7 @@ sub print_word_decimal0 (word: XY) -> (?) {
sub print_word_decimal (word: XY) -> (A?, X? Y?) {
; ---- print the word in X/Y in decimal form, without left padding 0s
asm {
%asm {
jsr word2decimal
ldy #0
lda word2decimal_output
@ -1011,7 +1011,7 @@ sub input_chars (buffer: AX) -> (A?, Y) {
; ---- Input a string (max. 80 chars) from the keyboard.
; It assumes the keyboard is selected as I/O channel!!
asm {
%asm {
sta c64.SCRATCH_ZP1
stx c64.SCRATCH_ZP2
ldy #0 ; char counter = 0
@ -1043,7 +1043,7 @@ sub input_chars (buffer: AX) -> (A?, Y) {
; ; - moving data to higher addresses works even if areas overlap
; ; - moving data to lower addresses only works if areas do not overlap
; ; @todo fix this
; asm {
; %asm {
; lda #<src_start
; ldx #>src_start
; sta $5f

View File

@ -9,7 +9,7 @@
~ il65_lib_zp {
; note: separate block so the 64tass assembler can remove this when no zp restore is required
asm {
%asm {
; ---- store the Zeropage in a backup area
save_zeropage
@ -62,7 +62,7 @@ zp_backup .fill 256, 0
memory .word SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
asm {
%asm {
; ---- jmp (indirect) routines for register pairs containing the indirect address
jsr_indirect_nozpuse_AX

View File

@ -22,7 +22,7 @@
sub multiply_bytes (byte1: X, byte2: Y) -> (A, X?) {
; ---- multiply 2 bytes, result as byte in A (signed or unsigned)
asm {
%asm {
stx SCRATCH_ZP1
sty SCRATCH_ZP2
ldx #8
@ -40,7 +40,7 @@ sub multiply_bytes (byte1: X, byte2: Y) -> (A, X?) {
sub multiply_bytes_16 (byte1: X, byte2: Y) -> (A?, XY) {
; ---- multiply 2 bytes, result as word in X/Y (unsigned)
asm {
%asm {
lda #0
_m_with_add stx SCRATCH_ZP1
sty SCRATCH_ZP2
@ -62,7 +62,7 @@ _m_with_add stx SCRATCH_ZP1
sub multiply_bytes_addA_16 (byte1: X, byte2: Y, add: A) -> (A?, XY) {
; ---- multiply 2 bytes and add A, result as word in X/Y (unsigned)
asm {
%asm {
jmp multiply_bytes_16._m_with_add
}
}
@ -73,7 +73,7 @@ sub multiply_words (number: XY) -> (?) {
; input: X/Y = first 16-bit number, SCRATCH_ZPWORD1 in ZP = second 16-bit number
; output: multiply_words_product 32-bits product, LSB order (low-to-high)
asm {
%asm {
stx SCRATCH_ZPWORD2
sty SCRATCH_ZPWORD2+1
@ -105,7 +105,7 @@ mult16 lda #$00
sub divmod_bytes (number: X, divisor: Y) -> (X, A) {
; ---- divide X by Y, result quotient in X, remainder in A (unsigned)
; division by zero will result in quotient = 255 and remainder = original number
asm {
%asm {
stx SCRATCH_ZP1
sty SCRATCH_ZP2
@ -131,7 +131,7 @@ sub divmod_words (divisor: XY) -> (A?, XY) {
; output: SCRATCH_ZPWORD1 in ZP: 16 bit result, X/Y: 16 bit remainder
; division by zero will result in quotient = 65535 and remainder = divident
asm {
%asm {
remainder = SCRATCH_ZP1
stx SCRATCH_ZPWORD2

View File

@ -19,7 +19,7 @@ which aims to provide many conveniences over raw assembly code (even when using
- automatic variable allocations, automatic string variables and string sharing
- automatic type conversions
- floating point operations
- automatically preserving and restoring CPU registers state, when calling routines that otherwise would clobber these
- optional automatic preserving and restoring CPU registers state, when calling routines that otherwise would clobber these
- abstracting away low level aspects such as zero page handling, program startup, explicit memory addresses
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
@ -39,7 +39,7 @@ You need [Python 3.5](https://www.python.org/downloads/) or newer to run IL65 it
IL65 is mainly targeted at the Commodore-64 machine, but should be mostly system independent.
MEMORY MODEL
Memory Model
------------
Most of the 64 kilobyte address space can be accessed by your program.
@ -123,7 +123,7 @@ and if blocks require it themselves you can't combine various modules anymore on
DATA TYPES
Data Types
----------
IL65 supports the following data types:
@ -168,11 +168,14 @@ unless it is part of a subroutine call statement. For an indirect goto call, the
(``jmp`` indirect) and an indirect subroutine call (``jsr`` indirect) is synthesized using a couple of instructions.
PROGRAM STRUCTURE
Program Structure
-----------------
In IL65 every line in the source file can only contain *one* statement or declaration.
In IL65 every line in the source file can only contain *one* statement or definitons.
Compilation is done on *one* main source code file, but other files can be imported.
A source file can start with global *directives* (starting with ``%``) and continues
with imports and block definitions.
### Comments
@ -181,44 +184,69 @@ If the comment is the only thing on the line, it is copied into the resulting as
This makes it easier to understand and relate the generated code.
### Output Modes
### Output Mode
The default format of the generated program is a "raw" binary where code starts at ``$c000``.
You can generate other types of programs as well, by telling IL65 via an output mode statement
You can generate other types of programs as well, by telling IL65 via an output mode directive
at the beginning of your program:
| mode declaration | meaning |
|----------------------|------------------------------------------------------------------------------------|
| ``output raw`` | no load address bytes |
| ``output prg`` | include the first two load address bytes, (default is ``$0801``), no BASIC program |
| ``output prg,basic`` | as 'prg', but include a BASIC start program with SYS call, default code start is immediately after the BASIC program at ``$081d``, or after that. |
| | |
| ``address $0801`` | override program start address (default is set to ``$c000`` for raw mode and ``$0801`` for C-64 prg mode). Cannot be used if output mode is ``prg,basic`` because BASIC programs always have to start at ``$0801``. |
| mode directive | meaning |
|-----------------------|------------------------------------------------------------------------------------|
| ``%output raw`` | no load address bytes |
| ``%output prg`` | include the first two load address bytes, (default is ``$0801``), no BASIC program |
| ``%output prg,basic`` | as 'prg', but include a BASIC start program with SYS call, default code start is immediately after the BASIC program at ``$081d``, or after that. |
| | |
| ``%address $0801`` | override program start address (default is set to ``$c000`` for raw mode and ``$0801`` for C-64 prg mode). Cannot be used if output mode is ``prg,basic`` because BASIC programs always have to start at ``$0801``. |
### ZeroPage Options
You can tell the compiler how to treat the *zero page*. Normally it is considered a 'no-go' area
except for the frew free locations mentioned under "Memory Model".
However you can specify some options globally in your program to change this behavior:
However you can specify some options globally in your program by using the zp directive
to change this behavior:
- ``zp clobber``
- ``%zp clobber``
Use the whole zeropage for variables. It is not possible to exit your program
correctly back to BASIC, other than resetting the machine.
It does make your program smaller and faster because many more variables can
be stored in the ZP, which is more efficient.
- ``zp clobber, restore``
- ``%zp clobber, restore``
Use the whole zeropage, but make a backup copy of the original values at program start.
When your program exits, the original ZP is restored (except for the software jiffy clock
in ``$a0 - $a2``) and you drop back to the BASIC prompt.
Not that the default IRQ routine is *still enabled* when your program is entered!
See the paragraph on the zero page for more info about this.
If you use ``zp clobber``, you can no longer use most BASIC or KERNAL routines,
If you use ``%zp clobber``, you can no longer use most BASIC or KERNAL routines,
because these depend on most of the locations in the ZP. This includes the floating-point
logic and several utility routines that do I/O, such as ``print_string``.
### Importing, Including and Binary-Including other files or raw assembly code
```%import "filename[.ill]"```
Must be used *after* any global option directives, and outside of a block,
but otherwise can be placed anywhere in the program.
Reads and compiles the named IL65 file and stores the resulting definitions
as if they were a part of your current program.
```%asminclude "filename.txt", scopelabel```
This directive can only be used inside a block.
The assembler will include the file as raw assembly source text at this point, il65 will not process this at all.
The scopelabel will be used as a prefix to access the labels from the included source code,
otherwise you would risk symbol redefinitions or duplications.
```%asmbinary "filename.bin" [, <offset>[, <length>]]```
This directive can only be used inside a block.
The assembler will include the file as binary bytes at this point, il65 will not process this at all.
The optional offset and length can be used to select a particular piece of the file.
```%asm {``` [raw assembly code lines] ``}``
This directive includes raw unparsed assembly code at that exact spot in the program.
The ``%asm {`` and ``}`` start and end markers each have to be on their own unique line.
### Program Entry Point
Every program has to have one entry point where code execution begins.
@ -229,40 +257,29 @@ Blocks and other details are described below.
### Blocks
~ blockname [address] {
statements
}
``~ blockname [address] {``
The blockname "ZP" is reserved and always means the ZeroPage. Its start address is always set to $04,
[directives...]
[statements...]
``}``
Blocks form the separate pieces of code and data of your program. They are combined and
arranged to a single output program. No code or data can occur outside a block.
A block is also a *scope* in your program so the symbols in the block don't clash with
symbols of the same name defined elsewhere. You can refer to the symbols in a particular block
by using a *dotted name*: ``blockname.symbolname``.
Block names must be unique in your entire program,
except "ZP": the contents of every block with that name are merged into one.
This block name refers to the zero page. Its start address is always set to $04,
because $00/$01 are used by the hardware and $02/$03 are reserved as general purpose scratch registers.
Block names cannot occur more than once, EXCEPT 'ZP' where the contents of every occurrence of it are merged.
Block address must be >= $0200 (because $00-$fff is the ZP and $100-$200 is the cpu stack)
You can omit the blockname but then you can only refer to the contents of the block via its absolute address,
which is required in this case. If you omit both, the block is ignored altogether (and a warning is displayed).
which is required in this case. If you omit *both*, the block is ignored altogether (and a warning is displayed).
This is a way to quickly "comment out" a piece of code that is unfinshed or may contain errors that you
want to work on later, because the contents of the ignored block are not syntactically parsed.
### Importing, Including and Binary-Including Files
import "filename[.ill]"
Must be used *after* any global option statements such as ``output``,
and can only be used outside of a block. But can otherwise occur between any blocks.
Reads everything from the named IL65 file at this point and compile it as a normal part of the program.
asminclude "filename.txt", scopelabel
Can only be used in a block.
The assembler will include the file as asm source text at this point, il65 will not process this at all.
The scopelabel will be used as a prefix to access the labels from the included source code,
otherwise you would risk symbol redefinitions or duplications.
asmbinary "filename.bin" [, <offset>[, <length>]]
Can only be used in a block.
The assembler will include the file as binary bytes at this point, il65 will not process this at all.
The optional offset and length can be used to select a particular piece of the file.
want to work on later, because the contents of the ignored block are not fully parsed.
### Assignments
@ -282,7 +299,6 @@ A special assignment is the *augmented assignment* where the value is modified i
Several assignment operators are available: ``+=``, ``-=``, ``&=``, ``|=``, ``^=``, ``<<=``, ``>>=``
### Expressions
In most places where a number or other value is expected, you can use just the number, or a full constant expression.
@ -294,7 +310,6 @@ all builtin functions (max, avg, min, sum etc). They can also reference idendifi
if this makes sense.
### Subroutine Definition
Subroutines are parts of the code that can be repeatedly invoked using a subroutine call from elsewhere.
@ -335,25 +350,24 @@ but instead assign a memory address to it:
### Subroutine Calling
You call a subroutine like this:
subroutinename_or_address [!] ( [arguments...] )
``subroutinename_or_address (`` [arguments...] ``)``
or:
``subroutinename_or_address !``[register(s)] ``( [arguments...] )``
If the subroutine returns one or more values as results, you must use an assignment statement
to store those values somewhere:
outputvar1, outputvar2 = subroutine ( arg1, arg2, arg3 )
``outputvar1, outputvar2 = subroutine ( arg1, arg2, arg3 )``
The output variables must occur in the correct sequence of return registers as specified
in the subroutine's definiton. It is possible to not specify any of them but the compiler
will issue a warning then if the result values of a subroutine call are discarded.
Even if the subroutine returns something in a register that already is the correct one
you want to keep, you'll have to explicitly assign the return value to that register.
If you omit it, no return value is stored at all (well, unless you call the subroutine without
register preserving, see the next paragraph.)
Normally, the registers are preserved when calling the subroutine and restored on return (except
the ones that explictly take result values from the call).
If you add a '!' after the name, no register preserving is done and the call essentially
is just a single JSR instruction.
Arguments should match the subroutine definition. You are allowed to omit the parameter names.
If no definition is available (because you're directly calling memory or a label or something else),
you can freely add arguments (but in this case they all have to be named).
@ -362,8 +376,40 @@ To jump to a subroutine (without returning), prefix the subroutine call with the
Unlike gotos in other languages, here it take arguments as well, because it
essentially is the same as calling a subroutine and only doing something different when it's finished.
**Register preserving calls:** use the ``!`` followed by a combination of A, X and Y (or followed
by nothing, which is the same as AXY) to tell the compiler you want to preserve the origial
value of the given registers after the subroutine call. Otherwise, the subroutine may just
as well clobber all three registers. Preserving the original values does result in some
stack manipulation code to be inserted for every call like this, which can be quite slow.
#### Calling Convention
Subroutine arguments and results are passed via registers (and sometimes implicitly
via certain memory locations).
@todo support call non-register args (variable parameter passing)
In IL65 the "caller saves" principle applies to registers used in a subroutine.
This means the code that calls a subroutine or performs some function that clobber certain
registers (A, X or Y), is responsible for storing and restoring the original values if
that is required.
*You should assume that the 3 hardware registers A, X and Y are volatile and their contents
cannot be depended upon, unless you make sure otherwise*.
Normally, the registers are NOT preserved when calling a subroutine or when a certian
operations are performed. Most calls will be simply a few instructions to load the
values in the registers and then a JSR or JMP.
By using the ``%preserve_registers`` directive (globally or in a block) you can tell the
compiler to preserve all registers. This does generate a lot of extra code that puts
original values on the stack and gets them off the stack again once the subroutine is done.
In this case however you don't have to worry about A, X and Y losing their original values
and you can essentially treat them as three local variables instead of scratch data.
You can also use a ``!`` on a single subroutine call to preserve register values, instead of
setting this behavior for the entire block or even program. See the paragraph on Subroutine Calling
for more info.
### Conditional Execution Flow
@ -394,7 +440,7 @@ if you run into this type of assembler error.
Debugging (with Vice)
---------------------
The ``breakpoint`` statement is a special statement that instructs the compiler to put
The ``%breakpoint`` directive instructs the compiler to put
a *breakpoint* at that position in the code. It's a logical breakpoint instead of a physical
BRK instruction because that will usually halt the machine altogether instead of breaking execution.
Instead, a NOP instruction is generated and in a special output file the list of breakpoints is written.
@ -409,7 +455,8 @@ so if your program runs and it hits a breakpoint, Vice will halt execution and d
TODOS
@Todo
-----
### IF_XX:
@ -549,12 +596,6 @@ these should call (or emit inline) optimized pieces of assembly code, so they ru
### Register Preservation Block
preserve [regs] { .... } adds register preservation around the containing code default = all 3 regs, or specify which.
nopreserve [regs] { .... } removes register preservation on all statements in the block that would otherwise have it.
### Bitmap Definition (for Sprites and Characters)
to define CHARACTERS (8x8 monochrome or 4x8 multicolor = 8 bytes)
@ -565,7 +606,9 @@ and SPRITES (24x21 monochrome or 12x21 multicolor = 63 bytes)
### More Datatypes
@todo 24 and 32 bits integers, unsigned and signed?
@todo pointers/addresses? (as opposed to normal WORDs)
@todo signed integers (byte and word)?
### Some support for simple arithmetic
@ -573,6 +616,3 @@ and SPRITES (24x21 monochrome or 12x21 multicolor = 63 bytes)
A = X * Y
A /= Y
A = Y / Y
@todo multiplication routines (8*8 -> 16, 8*16 -> 16, 16*16->16 (or 32?))
@todo division routines

View File

@ -59,7 +59,7 @@ bar
goto $c2
goto $c2()
asm {
%asm {
nop
nop
nop
@ -75,6 +75,15 @@ bar
sub4! ("hello", other=42)
sub4! (string="hello", other = 42)
sub4! (string="hello, there", other = 42)
sub3 (81)
sub3 !(81)
sub3 !A (81)
sub3 !X (81)
sub3 !Y (81)
sub3 !XY (81)
sub3 !AXY (81)
bar!()
[XY] ! ()
[var1] !()
@ -84,7 +93,7 @@ bar
$c000!()
$c2!()
asm {
%asm {
nop
nop
nop
@ -110,7 +119,7 @@ bar
$c2()
asm {
%asm {
nop
nop
nop

View File

@ -1,6 +1,6 @@
output prg,basic
%output prg,basic
import "c64lib"
%import "c64lib"
~ main {
var .word value

View File

@ -1,10 +1,10 @@
; var definitions and immediate primitive data type tests
output raw
zp clobber
%output raw
%zp clobber
import "c64lib"
%import "c64lib"
~ ZP {
; ZeroPage block definition:

View File

@ -1,8 +1,8 @@
; floating point tests
output prg, basic
%output prg, basic
import "c64lib"
%import "c64lib"
~ main_testing {
start

View File

@ -1,14 +1,15 @@
output prg,basic
%output prg,basic
import "c64lib"
%import "c64lib"
~ main {
var .text name = "?"*80
var .word orig_irq
start
c64.init_system()
XY = c64.CINV
orig_irq = c64.CINV
SI = 1
c64.CINV = #irq_handler
SI = 0
@ -19,27 +20,28 @@ start
c64.CHROUT('\n')
blop
breakpoint ; yeah!
%breakpoint ; yeah!
c64scr.print_string("thank you, mr or mrs: ")
c64scr.print_string(name)
c64.CHROUT('\n')
SI = 1
c64.CINV = XY
c64.CINV = orig_irq
SI = 0
return
irq_handler
asm {
lda $cb
cmp #$40
%asm {
lda c64.SFDX
cmp #$40 ; nothing pressed?
beq +
inc c64.EXTCOL
inc c64.EXTCOL ; otherwise change color
+ jmp c64.IRQDFRT
}
}

View File

@ -1,9 +1,9 @@
; var definitions and immediate primitive data type tests
output prg, basic
zp clobber
%output prg, basic
%zp clobber
import "c64lib"
%import "c64lib"
~ main {
start
@ -343,7 +343,7 @@ bar
goto $c2
goto $c2()
asm {
%asm {
nop
nop
nop
@ -368,7 +368,7 @@ bar
$c000!()
$c2!()
asm {
%asm {
nop
nop
nop
@ -394,7 +394,7 @@ bar
$c2()
asm {
%asm {
nop
nop
nop
@ -696,7 +696,7 @@ bar
goto $c2
goto $c2()
asm {
%asm {
nop
nop
nop
@ -721,7 +721,7 @@ bar
$c000!()
$c2!()
asm {
%asm {
nop
nop
nop
@ -747,7 +747,7 @@ bar
$c2()
asm {
%asm {
nop
nop
nop
@ -1049,7 +1049,7 @@ bar
goto $c2
goto $c2()
asm {
%asm {
nop
nop
nop
@ -1074,7 +1074,7 @@ bar
$c000!()
$c2!()
asm {
%asm {
nop
nop
nop
@ -1100,7 +1100,7 @@ bar
$c2()
asm {
%asm {
nop
nop
nop
@ -1402,7 +1402,7 @@ bar
goto $c2
goto $c2()
asm {
%asm {
nop
nop
nop
@ -1427,7 +1427,7 @@ bar
$c000!()
$c2!()
asm {
%asm {
nop
nop
nop
@ -1453,7 +1453,7 @@ bar
$c2()
asm {
%asm {
nop
nop
nop
@ -1755,7 +1755,7 @@ bar
goto $c2
goto $c2()
asm {
%asm {
nop
nop
nop
@ -1780,7 +1780,7 @@ bar
$c000!()
$c2!()
asm {
%asm {
nop
nop
nop
@ -1806,7 +1806,7 @@ bar
$c2()
asm {
%asm {
nop
nop
nop
@ -2108,7 +2108,7 @@ bar
goto $c2
goto $c2()
asm {
%asm {
nop
nop
nop
@ -2133,7 +2133,7 @@ bar
$c000!()
$c2!()
asm {
%asm {
nop
nop
nop
@ -2159,7 +2159,7 @@ bar
$c2()
asm {
%asm {
nop
nop
nop
@ -2461,7 +2461,7 @@ bar
goto $c2
goto $c2()
asm {
%asm {
nop
nop
nop
@ -2486,7 +2486,7 @@ bar
$c000!()
$c2!()
asm {
%asm {
nop
nop
nop
@ -2512,7 +2512,7 @@ bar
$c2()
asm {
%asm {
nop
nop
nop
@ -2814,7 +2814,7 @@ bar
goto $c2
goto $c2()
asm {
%asm {
nop
nop
nop
@ -2839,7 +2839,7 @@ bar
$c000!()
$c2!()
asm {
%asm {
nop
nop
nop
@ -2865,7 +2865,7 @@ bar
$c2()
asm {
%asm {
nop
nop
nop
@ -3167,7 +3167,7 @@ bar
goto $c2
goto $c2()
asm {
%asm {
nop
nop
nop
@ -3192,7 +3192,7 @@ bar
$c000!()
$c2!()
asm {
%asm {
nop
nop
nop
@ -3218,7 +3218,7 @@ bar
$c2()
asm {
%asm {
nop
nop
nop
@ -3520,7 +3520,7 @@ bar
goto $c2
goto $c2()
asm {
%asm {
nop
nop
nop
@ -3545,7 +3545,7 @@ bar
$c000!()
$c2!()
asm {
%asm {
nop
nop
nop
@ -3571,7 +3571,7 @@ bar
$c2()
asm {
%asm {
nop
nop
nop
@ -3873,7 +3873,7 @@ bar
goto $c2
goto $c2()
asm {
%asm {
nop
nop
nop
@ -3898,7 +3898,7 @@ bar
$c000!()
$c2!()
asm {
%asm {
nop
nop
nop
@ -3924,7 +3924,7 @@ bar
$c2()
asm {
%asm {
nop
nop
nop
@ -4226,7 +4226,7 @@ bar
goto $c2
goto $c2()
asm {
%asm {
nop
nop
nop
@ -4251,7 +4251,7 @@ bar
$c000!()
$c2!()
asm {
%asm {
nop
nop
nop
@ -4277,7 +4277,7 @@ bar
$c2()
asm {
%asm {
nop
nop
nop
@ -4579,7 +4579,7 @@ bar
goto $c2
goto $c2()
asm {
%asm {
nop
nop
nop
@ -4604,7 +4604,7 @@ bar
$c000!()
$c2!()
asm {
%asm {
nop
nop
nop
@ -4630,7 +4630,7 @@ bar
$c2()
asm {
%asm {
nop
nop
nop
@ -4932,7 +4932,7 @@ bar
goto $c2
goto $c2()
asm {
%asm {
nop
nop
nop
@ -4957,7 +4957,7 @@ bar
$c000!()
$c2!()
asm {
%asm {
nop
nop
nop
@ -4983,7 +4983,7 @@ bar
$c2()
asm {
%asm {
nop
nop
nop
@ -5285,7 +5285,7 @@ bar
goto $c2
goto $c2()
asm {
%asm {
nop
nop
nop
@ -5310,7 +5310,7 @@ bar
$c000!()
$c2!()
asm {
%asm {
nop
nop
nop
@ -5336,7 +5336,7 @@ bar
$c2()
asm {
%asm {
nop
nop
nop
@ -5638,7 +5638,7 @@ bar
goto $c2
goto $c2()
asm {
%asm {
nop
nop
nop
@ -5663,7 +5663,7 @@ bar
$c000!()
$c2!()
asm {
%asm {
nop
nop
nop
@ -5689,7 +5689,7 @@ bar
$c2()
asm {
%asm {
nop
nop
nop
@ -5991,7 +5991,7 @@ bar
goto $c2
goto $c2()
asm {
%asm {
nop
nop
nop
@ -6016,7 +6016,7 @@ bar
$c000!()
$c2!()
asm {
%asm {
nop
nop
nop
@ -6042,7 +6042,7 @@ bar
$c2()
asm {
%asm {
nop
nop
nop
@ -6344,7 +6344,7 @@ bar
goto $c2
goto $c2()
asm {
%asm {
nop
nop
nop
@ -6369,7 +6369,7 @@ bar
$c000!()
$c2!()
asm {
%asm {
nop
nop
nop
@ -6395,7 +6395,7 @@ bar
$c2()
asm {
%asm {
nop
nop
nop
@ -6697,7 +6697,7 @@ bar
goto $c2
goto $c2()
asm {
%asm {
nop
nop
nop
@ -6722,7 +6722,7 @@ bar
$c000!()
$c2!()
asm {
%asm {
nop
nop
nop
@ -6748,7 +6748,7 @@ bar
$c2()
asm {
%asm {
nop
nop
nop
@ -7050,7 +7050,7 @@ bar
goto $c2
goto $c2()
asm {
%asm {
nop
nop
nop
@ -7075,7 +7075,7 @@ bar
$c000!()
$c2!()
asm {
%asm {
nop
nop
nop
@ -7101,7 +7101,7 @@ bar
$c2()
asm {
%asm {
nop
nop
nop

View File

@ -1,8 +1,5 @@
output prg,basic
;reg_preserve off ; @todo global option off/on default off?
import "c64lib"
%output prg,basic
%import "c64lib"
~ main {

View File

@ -4,8 +4,8 @@
; line 3 comment
output basic , prg ; create a c-64 program with basic SYS call to launch it
zp restore , clobber ; clobber over the zp memory normally used by basic/kernel rom, frees up more zp
%output basic , prg ; create a c-64 program with basic SYS call to launch it
%zp restore , clobber ; clobber over the zp memory normally used by basic/kernel rom, frees up more zp
~main $0a00
@ -88,7 +88,7 @@ sub memsub () -> () = $fff2
sub customsub (Y)->() {
asm {
%asm {
nop
nop
lda #99
@ -120,7 +120,7 @@ somelabel1222
~ block3 {
somelabel1
asm {
%asm {
nop
nop
nop

View File

@ -1,71 +1,24 @@
; source IL file
; these are comments
%output prg
%address $c000
output prg, basic ; create a c-64 program with basic SYS call to launch it
zp clobber,restore ; clobber over the zp memory normally used by basic/kernel rom, frees up more zp
~main
{
memory borderb1 = $d020
memory .byte borderb2 = $d020
memory .word screencolors = $d020
memory .array($10) screenblock1 = $0400 ; 10 bytes
memory .array($0100) screenblock2 = $0500 ; 256 bytes
memory .array($0234) screenblock3 = $2000 ; some weird size
memory .wordarray($20) screenblock4 = $0600 ; 32 words
memory .matrix(40,25) charscreen = $0400
var .array($10) var1 = $aa
var .wordarray($10) var2 = $9988
var .array($10) var3 = $11
var var4
var .matrix(40,25) vcharscreen ; init with 0
var .matrix(40,25) vcharscreen2 = 111 ; init with 111
start
; set some colors
Y = 0
borderb1 = Y
X=Y=A = 0
X = 1
screencolors = X
borderb1 = 0
borderb2 = 1
screencolors = $0
screencolors = $0207
; fill block of memory with a single value (byte or word)
;screenblock1,* = A
;screenblock1,* = X
;screenblock1,* = Y
;screenblock1,* = 2
;screenblock1,* = $aaa
;screenblock2,* = A
;screenblock2,* = X
;screenblock2,* = Y
;screenblock2,* = 2
;screenblock2,* = $aaa
;screenblock3,* = A
;screenblock3,* = X
;screenblock3,* = Y
;screenblock3,* = 2
;screenblock3,* = $aaa
;screenblock4,* = A
;screenblock4,* = X
;screenblock4,* = Y
;screenblock4,* = 2
;screenblock4,* = $aaa
; swap bytes
;A = var1[0]
;X = var1[1]
;var1[0] = X
;var1[1] = A
; swap words
;A = var2[0] ; must crash, cannot put word in register
;X = var2[1] ;crash
;var2[0] = X ; ok, 00-padded
;var2[1] = A ; ok, 00-padded
return
~ test {
var .byte localvar = 33
return
}
~ main {
start
A=0
[$d020]=A
X=false
Y=true
return
;included_assembly
%asminclude "included.source" test_include
;included_binary
%asmbinary "included.binary"
%asmbinary "included.binary" $40
%asmbinary "included.binary" $40 $200
}

View File

@ -1,8 +1,8 @@
output prg,basic ; create a c-64 program with basic SYS call to launch it
%output prg,basic ; create a c-64 program with basic SYS call to launch it
import "c64lib" ; searched in several locations and with .ill file extension added
%import "c64lib" ; searched in several locations and with .ill file extension added
~ main
{

View File

@ -1,6 +1,6 @@
output prg,basic ; create a c-64 program with basic SYS to() launch it
%output prg,basic ; create a c-64 program with basic SYS to() launch it
import "c64lib.ill"
%import "c64lib.ill"
~ main
{
@ -9,21 +9,21 @@ output prg,basic ; create a c-64 program with basic SYS to() launch it
const .word BORDER = $d020
start
c64scr.print_pimmediate ! () ; this prints the pstring immediately following it
asm {
c64scr.print_pimmediate() ; this prints the pstring immediately following it
%asm {
.ptext "hello-pimmediate!{cr}"
}
c64scr.print_byte_decimal0 ! (19)
c64.CHROUT ! (13)
c64scr.print_byte_decimal ! (19)
c64.CHROUT ! (13)
c64scr.print_byte_decimal0 (19)
c64.CHROUT (13)
c64scr.print_byte_decimal (19)
c64.CHROUT (13)
c64scr.print_word_decimal0 ! ($0102)
c64.CHROUT ! (13)
c64scr.print_word_decimal ! ($0102)
c64.CHROUT ! (13)
c64scr.print_word_decimal0 ($0102)
c64.CHROUT (13)
c64scr.print_word_decimal ($0102)
c64.CHROUT (13)
return
start2

View File

@ -1,24 +0,0 @@
output prg
address $c000
~ test {
var .byte localvar = 33
return
}
~ main {
start
A=0
[$d020]=A
X=false
Y=true
return
;included_assembly
asminclude "included.source" test_include
;included_binary
asmbinary "included.binary"
asmbinary "included.binary" $40
asmbinary "included.binary" $40 $200
}

View File

@ -1,10 +1,12 @@
output prg,basic
%output prg,basic
%preserve_registers no
;reg_preserve off ; @todo global option off/on default off? NOT AN OPTION -> change the default to OFF
import "c64lib"
%import "c64lib"
~ main {
%preserve_registers false
const num = 2
var var1 =2
var .word wvar1 = 2
@ -41,7 +43,7 @@ start
;XY <<= 2
asm {
%asm {
ldy #200
- lda #81
sta c64.Screen+39-40,y
@ -92,10 +94,13 @@ loop
X = $22
Y = $33
c64scr.clear_screen !(81, 5) ; @todo new syntax to specify registers to save (! = all three A,X,Y)
;c64scr.clear_screen !A (81, 5) ; @todo new syntax to specify registers to save (only A)
;c64scr.clear_screen !AX (81, 5)
;c64scr.clear_screen !AXY (81, 5)
c64scr.clear_screen (81, 5)
c64scr.clear_screen !(81, 5)
c64scr.clear_screen !A (81, 5)
c64scr.clear_screen !X (81, 5)
c64scr.clear_screen !Y (81, 5)
c64scr.clear_screen !XY (81, 5)
c64scr.clear_screen !AXY (81, 5)
c64scr.print_byte_hex(1,A)
c64.CHROUT(' ')