Vice labels and breakpoints

This commit is contained in:
Irmen de Jong 2017-12-27 23:45:22 +01:00
parent 44065597ff
commit f5c7573fb3
6 changed files with 86 additions and 15 deletions

View File

@ -12,6 +12,7 @@ which aims to provide many conveniences over raw assembly code (even when using
- reduction of source code length
- easier program understanding (because it's higher level, and more terse)
- option to automatically run the compiled program in the Vice emulator
- modularity, symbol scoping, subroutines
- subroutines have enforced input- and output parameter definitions
- automatic variable allocations
@ -20,7 +21,10 @@ which aims to provide many conveniences over raw assembly code (even when using
- floating point operations
- automatically 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
- @todo: conditionals and loops
- 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
- conditional gotos
- @todo: loops
- @todo: memory block operations
It still allows for low level programming however and inline assembly blocks

View File

@ -7,6 +7,7 @@ License: GNU GPL 3.0, see LICENSE
"""
import io
import re
import math
import datetime
import subprocess
@ -23,6 +24,9 @@ class CodeError(Exception):
class CodeGenerator:
BREAKPOINT_COMMENT_SIGNATURE = "~~~BREAKPOINT~~~"
BREAKPOINT_COMMENT_DETECTOR = r".(?P<address>\w+)\s+ea\s+nop\s+;\s+{:s}.*".format(BREAKPOINT_COMMENT_SIGNATURE)
def __init__(self, parsed: ParseResult) -> None:
self.parsed = parsed
self.generated_code = io.StringIO()
@ -382,6 +386,9 @@ class CodeGenerator:
self.p("\t\t; end inline asm, src l. {:d}".format(stmt.lineno))
elif isinstance(stmt, ParseResult.Comment):
self.p(stmt.text)
elif isinstance(stmt, ParseResult.BreakpointStmt):
# put a marker in the source so that we can generate a list of breakpoints later
self.p("\t\tnop\t; {:s} src l. {:d}".format(self.BREAKPOINT_COMMENT_SIGNATURE, stmt.lineno))
else:
raise CodeError("unknown statement " + repr(stmt))
self.previous_stmt_was_assignment = isinstance(stmt, ParseResult.AssignmentStmt)
@ -504,7 +511,7 @@ class CodeGenerator:
else:
raise CodeError("invalid lvalue type in comparison", lv)
else:
# the condition is just the 'truth value' of the single value, or rather bool(value)
# the condition is just the 'truth value' of the single value,
# this is translated into assembly by comparing the argument to zero.
cv = stmt.condition.lvalue
inverse_status = stmt.condition.inverse_ifstatus()
@ -520,7 +527,7 @@ class CodeGenerator:
opcode = self._branchopcode(inverse_status)
if opcode not in ("beq", "bne"):
raise CodeError("cannot yet generate code for register pair that is not a true/false/eq/ne comparison",
self.cur_block.sourceref.file, stmt.lineno) # @todo
self.cur_block.sourceref.file, stmt.lineno) # @todo more register pair comparisons
if cv.register == 'AX':
line_after_goto = "+"
self.p("\t\tcmp #0")
@ -548,14 +555,14 @@ class CodeGenerator:
opcode = self._branchopcode(inverse_status)
if opcode not in ("beq", "bne"):
raise CodeError("cannot yet generate code for word value that is not a true/false/eq/ne comparison",
self.cur_block.sourceref.file, stmt.lineno) # @todo
self.cur_block.sourceref.file, stmt.lineno) # @todo more word value comparisons
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
self.p("\t\tlda " + (cv.name or Parser.to_hex(cv.address)))
self.p("\t\t{:s} +".format(opcode))
self.p("\t\tlda " + (cv.name or Parser.to_hex(cv.address)))
line_after_goto = "+\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1) # restore A
else:
raise CodeError("conditions cannot yet use other types than byte or word", # XXX
raise CodeError("conditions cannot yet use other types than byte or word", # @todo comparisons of other types
str(cv), self.cur_block.sourceref.file, stmt.lineno)
else:
raise CodeError("need register or memmapped value")
@ -573,9 +580,9 @@ class CodeGenerator:
elif status == "lt":
status = "cc"
elif status == "gt":
status = "eq + cs" # @todo
status = "eq + cs" # @todo if_gt
elif status == "le":
status = "cc + eq" # @todo
status = "cc + eq" # @todo if_le
elif status == "ge":
status = "cs"
opcodes = {"cc": "bcc",
@ -584,8 +591,8 @@ class CodeGenerator:
"vs": "bvs",
"eq": "beq",
"ne": "bne",
"pos": "bpl", # @todo correct?
"neg": "bmi"} # @todo correct?
"pos": "bpl",
"neg": "bmi"}
return opcodes[status]
def _generate_call(self, stmt: ParseResult.CallStmt, conditional_goto_opcode: str=None, line_after_goto: str=None) -> None:
@ -1191,8 +1198,9 @@ class Assembler64Tass:
self.format = format
def assemble(self, inputfilename: str, outputfilename: str) -> None:
args = ["64tass", "--ascii", "--case-sensitive", "-Wall", "-Wno-strict-bool", "--dump-labels",
"--labels", outputfilename+".labels.txt", "--output", outputfilename, inputfilename]
args = ["64tass", "--ascii", "--case-sensitive", "-Wall", "-Wno-strict-bool",
"--dump-labels", "--vice-labels", "-l", outputfilename+".vice-mon-list",
"-L", outputfilename+".final-asm", "--no-monitor", "--output", outputfilename, inputfilename]
if self.format == ProgramFormat.PRG:
args.append("--cbm-prg")
elif self.format == ProgramFormat.RAW:
@ -1210,3 +1218,19 @@ class Assembler64Tass:
raise SystemExit("ERROR: cannot run assembler program: "+str(x))
except subprocess.CalledProcessError as x:
print("assembler failed with returncode", x.returncode)
def generate_breakpoint_list(self, program_filename: str) -> str:
breakpoints = []
with open(program_filename + ".final-asm", "rU") as f:
for line in f:
match = re.fullmatch(CodeGenerator.BREAKPOINT_COMMENT_DETECTOR, line, re.DOTALL)
if match:
breakpoints.append("$" + match.group("address"))
cmdfile = program_filename + ".vice-mon-list"
with open(cmdfile, "at") as f:
print("; vice monitor breakpoint list now follows", file=f)
print("; {:d} breakpoints have been defined here".format(len(breakpoints)), file=f)
print("del", file=f)
for b in breakpoints:
print("break", b, file=f)
return cmdfile

View File

@ -11,6 +11,7 @@ License: GNU GPL 3.0, see LICENSE
import time
import os
import argparse
import subprocess
from .parse import Parser, Optimizer
from .preprocess import PreprocessingParser
from .codegen import CodeGenerator, Assembler64Tass
@ -20,7 +21,8 @@ def main() -> None:
description = "Compiler for IL65 language, code name 'Sick'"
ap = argparse.ArgumentParser(description=description)
ap.add_argument("-o", "--output", help="output directory")
ap.add_argument("--noopt", action="store_true", help="do not optimize the parse tree")
ap.add_argument("-no", "--nooptimize", action="store_true", help="do not optimize the parse tree")
ap.add_argument("-sv", "--startvice", action="store_true", help="autostart vice x64 emulator after compilation")
ap.add_argument("sourcefile", help="the source .ill/.il65 file to compile")
args = ap.parse_args()
assembly_filename = os.path.splitext(args.sourcefile)[0] + ".asm"
@ -40,7 +42,7 @@ def main() -> None:
p = Parser(args.sourcefile, args.output, sourcelines, ppsymbols=symbols, sub_usage=pp.result.subroutine_usage)
parsed = p.parse()
if parsed:
if args.noopt:
if args.nooptimize:
print("not optimizing the parse tree!")
else:
opt = Optimizer(parsed)
@ -52,7 +54,14 @@ def main() -> None:
cg.write_assembly(out)
assembler = Assembler64Tass(parsed.format)
assembler.assemble(assembly_filename, program_filename)
mon_command_file = assembler.generate_breakpoint_list(program_filename)
duration_total = time.perf_counter() - start
print("Compile duration: {:.2f} seconds".format(duration_total))
print("Output file: ", program_filename)
print()
if args.startvice:
print("Autostart vice emulator...")
args = ["x64", "-remotemonitor", "-moncommands", mon_command_file,
"-autostartprgmode", "1", "-autostart-warp", "-autostart", program_filename]
with open(os.devnull, "wb") as shutup:
subprocess.call(args, stdout=shutup)

View File

@ -499,6 +499,10 @@ class ParseResult:
self.lvalue, self.comparison_op, self.rvalue = self.rvalue, self.SWAPPED_OPERATOR[self.comparison_op], self.lvalue
return self.lvalue, self.comparison_op, self.rvalue
class BreakpointStmt(_AstNode):
def __init__(self, lineno: int) -> None:
super().__init__(lineno)
def add_block(self, block: 'ParseResult.Block', position: Optional[int]=None) -> None:
if position is not None:
self.blocks.insert(position, block)
@ -951,6 +955,9 @@ class Parser:
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(ParseResult.BreakpointStmt(self.sourceref.line))
self.print_warning("warning: {}: breakpoint defined".format(self.sourceref))
elif unstripped_line.startswith((" ", "\t")):
if is_zp_block:
raise self.PError("ZP block cannot contain code statements")

View File

@ -12,17 +12,22 @@ which aims to provide many conveniences over raw assembly code (even when using
- reduction of source code length
- easier program understanding (because it's higher level, and more terse)
- option to automatically run the compiled program in the Vice emulator
- modularity, symbol scoping, subroutines
- subroutines have enforced input- and output parameter definitions
- automatic variable allocations
- various data types other than just bytes
- automatic type conversions
- floating point operations
- automatically preserving and restoring CPU registers state, when calling routines that otherwise would clobber these
- automatically 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
- @todo: conditionals and loops
- 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
- conditional gotos
- @todo: loops
- @todo: memory block operations
It still allows for low level programming however and inline assembly blocks
to write performance critical pieces of code, but otherwise compiles fairly straightforwardly
into 6502 assembly code. This resulting code is assembled into a binary program by using
@ -306,6 +311,23 @@ essentially is the same as calling a subroutine and only doing something differe
@todo support call non-register args (variable parameter passing)
DEBUGGING (with Vice)
---------------------
The ``breakpoint`` statement is a special statement that 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.
This file is called "yourprogramname.vice-mon-list" and is meant to be used by the Vice C-64 emulator.
It contains a series of commands for Vice's monitor, this includes source labels and the breakpoint settings.
If you use the vice autostart feature of the compiler, it will be automatically processed by Vice.
If you launch Vice manually, you can use a command line option to load the file: ``x64 -moncommands yourprogramname.vice-mon-list``
Vice will use the label names in memory disassembly, and will activate the breakpoints as well
so if your program runs and it hits a breakpoint, Vice will halt execution and drop into the monitor.
TODOS
-----

View File

@ -13,9 +13,14 @@ start
c64.CINV = #irq_handler
SI = 0
c64util.print_string("enter your name: ")
c64util.input_chars(name)
c64.CHROUT('\n')
blop
breakpoint ; yeah!
c64util.print_string("thank you, mr or mrs: ")
c64util.print_string(name)
c64.CHROUT('\n')