mirror of
https://github.com/irmen/prog8.git
synced 2025-01-26 19:30:59 +00:00
Vice labels and breakpoints
This commit is contained in:
parent
44065597ff
commit
f5c7573fb3
@ -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
|
||||
|
@ -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
|
||||
|
13
il65/main.py
13
il65/main.py
@ -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)
|
||||
|
@ -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")
|
||||
|
26
reference.md
26
reference.md
@ -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
|
||||
-----
|
||||
|
||||
|
@ -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')
|
||||
|
Loading…
x
Reference in New Issue
Block a user