use super::{Backend, Dependency, DependencyCheck, SpawnConfig, shell_escape};
use crate::daemon::session_tmp;
pub struct JitDasmBackend;
impl Backend for JitDasmBackend {
fn name(&self) -> &'static str {
"jitdasm"
}
fn description(&self) -> &'static str {
".NET JIT disassembly analyzer"
}
fn types(&self) -> &'static [&'static str] {
&["jitdasm"]
}
fn spawn_config(&self, target: &str, args: &[String]) -> anyhow::Result<SpawnConfig> {
let project = target.to_string();
let out_dir = session_tmp("jitdasm");
let out_dir_str = out_dir.display().to_string();
let out_file = out_dir.join("capture.asm");
let out_file_str = out_file.display().to_string();
let pattern = if args.is_empty() {
"*".to_string() } else {
args[0].clone()
};
let extra_args: Vec<String> = if args.len() > 1 {
args[1..].to_vec()
} else {
vec![]
};
let extra = if extra_args.is_empty() {
String::new()
} else {
let escaped: Vec<String> = extra_args.iter().map(|a| shell_escape(a)).collect();
format!(" {}", escaped.join(" "))
};
let dbg_bin = super::self_exe();
let mkdir_cmd = format!("mkdir -p {}", out_dir_str);
let build_cmd = format!(
"echo 'Building...' && dotnet build {} -c Release --nologo -v q 2>&1 | tail -1",
shell_escape(&project)
);
let run_cmd = format!(
"echo 'Disassembling: {}' && DOTNET_TieredCompilation=0 DOTNET_JitDisasm='{}' DOTNET_JitDiffableDasm=1 dotnet run --project {} -c Release --no-build{} > {} 2>&1",
pattern, pattern, shell_escape(&project), extra, out_file_str
);
let exec_repl = format!("exec {} --jitdasm-repl {}", dbg_bin, out_file_str);
Ok(SpawnConfig {
bin: "bash".into(),
args: vec!["--norc".into(), "--noprofile".into()],
env: vec![("PS1".into(), "jitdasm> ".into())],
init_commands: vec![mkdir_cmd, build_cmd, run_cmd, exec_repl],
})
}
fn prompt_pattern(&self) -> &str {
r"jitdasm> $"
}
fn dependencies(&self) -> Vec<Dependency> {
vec![Dependency {
name: "dotnet",
check: DependencyCheck::Binary {
name: "dotnet",
alternatives: &["dotnet"],
version_cmd: None,
},
install: "https://dot.net/install",
}]
}
fn run_command(&self) -> &'static str {
"stats"
}
fn quit_command(&self) -> &'static str {
"exit"
}
fn parse_help(&self, _raw: &str) -> String {
"jitdasm: methods, disasm <pattern>, search <instr>, stats, hotspots [N], simd, help".to_string()
}
fn adapters(&self) -> Vec<(&'static str, &'static str)> {
vec![("jitdasm.md", include_str!("../../skills/adapters/jitdasm.md"))]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn spawn_config_with_pattern() {
let cfg = JitDasmBackend
.spawn_config("bench/tq-quick/tq-quick.csproj", &["SimdOps:DotProduct".into()])
.unwrap();
assert_eq!(cfg.bin, "bash");
assert!(cfg.init_commands[0].contains("mkdir"));
assert!(cfg.init_commands[1].contains("dotnet build"));
assert!(cfg.init_commands[2].contains("DOTNET_JitDisasm"));
assert!(cfg.init_commands[2].contains("SimdOps:DotProduct"));
}
#[test]
fn spawn_config_default_captures_all() {
let cfg = JitDasmBackend
.spawn_config("myapp.csproj", &[])
.unwrap();
assert!(cfg.init_commands[2].contains("DOTNET_JitDisasm='*'"));
}
#[test]
fn spawn_config_with_extra_args() {
let cfg = JitDasmBackend
.spawn_config(
"myapp.csproj",
&["MyMethod".into(), "--ef".into(), "64".into()],
)
.unwrap();
assert!(cfg.init_commands[2].contains("--ef 64"));
}
#[test]
fn spawn_config_execs_repl() {
let cfg = JitDasmBackend
.spawn_config("myapp.csproj", &["Foo:Bar".into()])
.unwrap();
assert!(cfg.init_commands[3].contains("--jitdasm-repl"));
assert!(cfg.init_commands[3].contains("exec"));
}
#[test]
fn prompt_pattern_matches() {
let re = regex::Regex::new(JitDasmBackend.prompt_pattern()).unwrap();
assert!(re.is_match("jitdasm> "));
}
#[test]
fn format_breakpoint_empty() {
assert_eq!(JitDasmBackend.format_breakpoint("anything"), "");
}
}