mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 08:55:38 +00:00
CLI: --call-graph flag
Prints a tree view of every function/handler and its direct
callees, plus the max call depth reached from each state-handler
entry point. Useful for stack-budget investigation since the NES
has only 256 bytes of stack.
=== Call Graph (max depth: 2 / 8) ===
Main::frame (max depth 2)
├── clamp
├── clamp
└── check_collision
check_collision
├── abs_diff
└── abs_diff
abs_diff
└── (leaf)
clamp
└── (leaf)
Max-depth labels are only shown on entry points where the analyzer
has computed a depth; transitive callees print without a label so
the output isn't confusing.
https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
This commit is contained in:
parent
db234a6ca7
commit
6e007774e4
2 changed files with 53 additions and 0 deletions
|
|
@ -944,6 +944,7 @@ nescript build game.ne --dump-ir
|
|||
| `--asm-dump` | Dump generated 6502 assembly to stdout |
|
||||
| `--dump-ir` | Dump the lowered IR program (after optimization) to stdout |
|
||||
| `--memory-map` | Dump a memory map of variable allocations to stdout |
|
||||
| `--call-graph` | Dump a call graph (which handler/function calls which) to stdout |
|
||||
| `--use-ast` | Use the legacy AST-based codegen (default is the IR codegen) |
|
||||
|
||||
### Check
|
||||
|
|
|
|||
52
src/main.rs
52
src/main.rs
|
|
@ -38,6 +38,10 @@ enum Cli {
|
|||
#[arg(long)]
|
||||
memory_map: bool,
|
||||
|
||||
/// Dump a call graph showing which functions call which.
|
||||
#[arg(long)]
|
||||
call_graph: bool,
|
||||
|
||||
/// Use the legacy AST-based codegen. The default is the IR-based
|
||||
/// codegen, which runs the optimizer passes before emitting 6502.
|
||||
#[arg(long)]
|
||||
|
|
@ -61,6 +65,7 @@ fn main() {
|
|||
asm_dump,
|
||||
dump_ir,
|
||||
memory_map,
|
||||
call_graph,
|
||||
use_ast,
|
||||
} => {
|
||||
let output = output.unwrap_or_else(|| input.with_extension("nes"));
|
||||
|
|
@ -71,6 +76,7 @@ fn main() {
|
|||
asm_dump,
|
||||
dump_ir,
|
||||
memory_map,
|
||||
call_graph,
|
||||
use_ast,
|
||||
},
|
||||
) {
|
||||
|
|
@ -155,6 +161,46 @@ fn print_memory_map(analysis: &nescript::analyzer::AnalysisResult) {
|
|||
println!("Main RAM: {ram_used}/1280 bytes used");
|
||||
}
|
||||
|
||||
/// Print a human-readable call graph of the analyzed program.
|
||||
/// Entries show the max call depth reached from each entry point
|
||||
/// (state handler) and the transitive callees.
|
||||
fn print_call_graph(analysis: &nescript::analyzer::AnalysisResult) {
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
let sorted: BTreeMap<_, _> = analysis
|
||||
.call_graph
|
||||
.iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect();
|
||||
let max_depth = analysis.max_depths.values().copied().max().unwrap_or(0);
|
||||
|
||||
println!("=== Call Graph (max depth: {max_depth} / 8) ===");
|
||||
if sorted.is_empty() {
|
||||
println!(" (no functions or handlers)");
|
||||
return;
|
||||
}
|
||||
for (caller, callees) in &sorted {
|
||||
if let Some(depth) = analysis.max_depths.get(caller) {
|
||||
println!("{caller} (max depth {depth})");
|
||||
} else {
|
||||
println!("{caller}");
|
||||
}
|
||||
if callees.is_empty() {
|
||||
println!(" └── (leaf)");
|
||||
} else {
|
||||
let count = callees.len();
|
||||
for (i, callee) in callees.iter().enumerate() {
|
||||
let branch = if i + 1 == count {
|
||||
"└──"
|
||||
} else {
|
||||
"├──"
|
||||
};
|
||||
println!(" {branch} {callee}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dump_asm(instructions: &[nescript::asm::Instruction]) {
|
||||
use nescript::asm::{AddressingMode, Opcode};
|
||||
for inst in instructions {
|
||||
|
|
@ -179,6 +225,7 @@ struct CompileOptions {
|
|||
asm_dump: bool,
|
||||
dump_ir: bool,
|
||||
memory_map: bool,
|
||||
call_graph: bool,
|
||||
use_ast: bool,
|
||||
}
|
||||
|
||||
|
|
@ -187,6 +234,7 @@ fn compile(input: &PathBuf, opts: &CompileOptions) -> Result<Vec<u8>, ()> {
|
|||
let asm_dump = opts.asm_dump;
|
||||
let dump_ir = opts.dump_ir;
|
||||
let memory_map = opts.memory_map;
|
||||
let call_graph = opts.call_graph;
|
||||
let use_ast = opts.use_ast;
|
||||
let raw_source = std::fs::read_to_string(input).map_err(|e| {
|
||||
eprintln!("error: failed to read {}: {e}", input.display());
|
||||
|
|
@ -237,6 +285,10 @@ fn compile(input: &PathBuf, opts: &CompileOptions) -> Result<Vec<u8>, ()> {
|
|||
print_memory_map(&analysis);
|
||||
}
|
||||
|
||||
if call_graph {
|
||||
print_call_graph(&analysis);
|
||||
}
|
||||
|
||||
// Resolve sprite assets (CHR data + tile indices) relative to the
|
||||
// source file's directory, so `@binary` / `@chr` paths work naturally.
|
||||
let source_dir = input.parent().unwrap_or_else(|| Path::new("."));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue