1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-17 14:16:11 +00:00

linker: add ca65-compatible --dbg output for source-level debugging

Emit a `.dbg` debug-info file in the same format `ld65` produces, so
Mesen / Mesen2 / fceuX pick it up automatically and enable source-line
stepping, labelled variable inspection, and symbol-based breakpoints
without manual address lookups. Closes #23.

The new `render_dbg` helper stitches together metadata the compiler
already surfaces (linker label table, IR codegen `__src_<N>` markers,
analyzer variable allocations) into the file/mod/seg/scope/span/line/sym
records documented at https://cc65.github.io/doc/debugfile.html. Each
source-loc marker becomes a span that stretches to the next marker
(so breakpoints cover every byte the statement compiled into) plus a
line record pointing into it; `seg.ooffs` tracks the fixed bank's
PRG-relative start so banked MMC1/UxROM/MMC3 ROMs map cleanly too.

Reuses the `.mlb` symbol-name filter so internal skip/block labels
stay out of the debugger's symbol browser. `--dbg` implies the same
`__src_` marker emission as `--source-map` but leaves release builds
byte-identical when neither flag is passed.

https://claude.ai/code/session_01DfN3pKJLryr7vvNFBpcqmC
This commit is contained in:
Claude 2026-04-16 22:39:08 +00:00
parent f9198ac52c
commit e4751df143
No known key found for this signature in database
6 changed files with 566 additions and 16 deletions

View file

@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
use nescript::analyzer;
use nescript::assets::{BackgroundData, PaletteData};
use nescript::errors::render_diagnostics;
use nescript::linker::{render_mlb, render_source_map, LinkedRom};
use nescript::linker::{render_dbg, render_mlb, render_source_map, LinkedRom};
use nescript::pipeline::{compile_source, CompileError, CompileOptions as PipelineOptions};
#[derive(Parser)]
@ -63,6 +63,16 @@ enum Cli {
/// reverse-mapping a crash address back to the source.
#[arg(long, value_name = "PATH")]
source_map: Option<PathBuf>,
/// Write a ca65-compatible debug-info file (`.dbg`) next
/// to the ROM. The format is the same one `ld65` emits,
/// so Mesen / Mesen2 / fceuX pick it up automatically and
/// enable source-level stepping, labelled variable
/// inspection, and symbol-based breakpoints. Implies
/// `--source-map`-style `__src_<N>` marker emission so
/// line records have something to point at.
#[arg(long, value_name = "PATH")]
dbg: Option<PathBuf>,
},
/// Type-check a source file without building
Check {
@ -86,10 +96,12 @@ fn main() {
no_opt,
symbols,
source_map,
dbg,
} => {
let output = output.unwrap_or_else(|| input.with_extension("nes"));
match compile(
&input,
&output,
&CompileOptions {
debug,
asm_dump,
@ -99,6 +111,7 @@ fn main() {
no_opt,
symbols: symbols.clone(),
source_map: source_map.clone(),
dbg: dbg.clone(),
},
) {
Ok(rom) => {
@ -334,9 +347,10 @@ struct CompileOptions {
no_opt: bool,
symbols: Option<PathBuf>,
source_map: Option<PathBuf>,
dbg: Option<PathBuf>,
}
fn compile(input: &PathBuf, opts: &CompileOptions) -> Result<Vec<u8>, ()> {
fn compile(input: &PathBuf, output: &Path, opts: &CompileOptions) -> Result<Vec<u8>, ()> {
// File I/O + preprocessing lives here so the pipeline module
// itself doesn't need to touch `std::fs`. That keeps the
// pipeline usable from a future WASM host that routes asset
@ -356,10 +370,14 @@ fn compile(input: &PathBuf, opts: &CompileOptions) -> Result<Vec<u8>, ()> {
// needs a new feature (new flag, new output, whatever), the
// change lands in `pipeline::compile_source` and every
// caller picks it up automatically.
//
// `--dbg` reuses the same `__src_<N>` markers that
// `--source-map` emits, so either flag flips on source-loc
// emission in the codegen.
let pipeline_opts = PipelineOptions {
debug: opts.debug,
no_opt: opts.no_opt,
emit_source_map: opts.source_map.is_some(),
emit_source_map: opts.source_map.is_some() || opts.dbg.is_some(),
};
let out = compile_source(&source, source_dir, &pipeline_opts).map_err(|e| match e {
CompileError::Parse(diags) => {
@ -426,6 +444,19 @@ fn compile(input: &PathBuf, opts: &CompileOptions) -> Result<Vec<u8>, ()> {
eprintln!("error: failed to write source map {}: {e}", path.display());
})?;
}
if let Some(path) = opts.dbg.as_ref() {
let dbg = render_dbg(
&out.link_result,
&out.source_locs,
&out.analysis.var_allocations,
&source,
input,
output,
);
std::fs::write(path, dbg).map_err(|e| {
eprintln!("error: failed to write dbg file {}: {e}", path.display());
})?;
}
Ok(out.rom)
}