diff --git a/crates/oxide_cli/src/main.rs b/crates/oxide_cli/src/main.rs index 54e1c0c3..22884690 100644 --- a/crates/oxide_cli/src/main.rs +++ b/crates/oxide_cli/src/main.rs @@ -46,11 +46,17 @@ struct Cli { enum Commands { Eval { code: String, + /// 逐指令 trace:每条指令向 stderr 写一行 pc + opcode + 操作数。 + #[arg(long)] + trace: bool, }, Run { file: String, #[arg(long, default_value = "1")] repeat: u64, + /// 逐指令 trace:每条指令向 stderr 写一行 pc + opcode + 操作数。 + #[arg(long)] + trace: bool, }, Compile { #[arg(short = 'e')] @@ -87,16 +93,16 @@ fn main() -> ExitCode { let cli = Cli::parse(); match cli.command { - Some(Commands::Eval { code }) => { + Some(Commands::Eval { code, trace }) => { let kernel = make_kernel(cli.verbose, cli.quiet); let pool = make_pool(&kernel); - eval(&code, &kernel, &pool) + eval(&code, &kernel, &pool, trace) } - Some(Commands::Run { file, repeat }) => { + Some(Commands::Run { file, repeat, trace }) => { let kernel = make_kernel(cli.verbose, cli.quiet); let pool = make_pool(&kernel); for n in 0..repeat { - let code = run(&file, &kernel, &pool); + let code = run(&file, &kernel, &pool, trace); if code != ExitCode::SUCCESS && code != ExitCode::FAILURE { return code; } @@ -155,7 +161,7 @@ fn make_pool(kernel: &Arc) -> Arc { VmPool::new(Arc::clone(kernel), kernel.config.min_pool_size, kernel.config.max_pool_size) } -fn eval(code: &str, kernel: &Arc, pool: &Arc) -> ExitCode { +fn eval(code: &str, kernel: &Arc, pool: &Arc, trace: bool) -> ExitCode { let allocator = Allocator::default(); let program = match oxide_parser::parse(&allocator, code) { Ok(p) => p, @@ -180,6 +186,7 @@ fn eval(code: &str, kernel: &Arc, pool: &Arc) -> ExitCode { }; let mut guard = pool.spawn(); + guard.vm_mut().set_instruction_trace(trace); match guard.vm_mut().run(&module) { Ok(result) => { format_result(guard.vm(), kernel.perm_interner().as_ref(), kernel.shape_forge().as_ref(), result); @@ -284,9 +291,9 @@ fn format_array( format!("[{}]", items.join(", ")) } -fn run(file: &str, kernel: &Arc, pool: &Arc) -> ExitCode { +fn run(file: &str, kernel: &Arc, pool: &Arc, trace: bool) -> ExitCode { match fs::read_to_string(file) { - Ok(source) => eval(&source, kernel, pool), + Ok(source) => eval(&source, kernel, pool, trace), Err(err) => { kernel_error!("cannot read {}: {}", file, err); eprintln!("{}", Red.paint(format!("Cannot read {file}: {err}"))); diff --git a/crates/oxide_cli/tests/cli_smoke.rs b/crates/oxide_cli/tests/cli_smoke.rs index 282bca71..56f5692f 100644 --- a/crates/oxide_cli/tests/cli_smoke.rs +++ b/crates/oxide_cli/tests/cli_smoke.rs @@ -47,3 +47,34 @@ fn bench_help() { let stdout = String::from_utf8_lossy(&output.stdout); assert!(stdout.contains("--iterations"), "bench --help should list --iterations flag"); } + +#[test] +fn eval_trace_flag_pc_lines() { + let output = Command::new(env!("CARGO_BIN_EXE_oxide")) + .args(["eval", "--trace", "1 + 2"]) + .output() + .expect("failed to run oxide eval --trace"); + + assert!(output.status.success(), "eval --trace should exit 0"); + // trace 行只走 stderr,stdout 保持纯结果。 + let stdout = String::from_utf8_lossy(&output.stdout); + assert_eq!(stdout.trim(), "3", "--trace 不应污染 stdout: {stdout}"); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!(stderr.lines().all(|l| l.starts_with("pc=")), "trace 行应全部为 pc 行: {stderr}"); + assert!(stderr.contains("LOAD_CONST"), "trace 应含 LOAD_CONST: {stderr}"); + assert!(stderr.contains("ADD"), "trace 应含 ADD: {stderr}"); + assert!(stderr.contains("HALT"), "trace 应含 HALT: {stderr}"); +} + +#[test] +fn eval_no_trace_flag_no_pc_lines() { + let output = Command::new(env!("CARGO_BIN_EXE_oxide")) + .args(["eval", "1 + 2"]) + .output() + .expect("failed to run oxide eval"); + + assert!(output.status.success(), "eval should exit 0"); + // 默认路径零 trace 输出。 + let stderr = String::from_utf8_lossy(&output.stderr); + assert!(!stderr.contains("pc="), "默认路径不应有 trace 行: {stderr}"); +} diff --git a/crates/oxide_vm/src/vm/dispatch_loop.rs b/crates/oxide_vm/src/vm/dispatch_loop.rs index 183b8bfb..add49f4c 100644 --- a/crates/oxide_vm/src/vm/dispatch_loop.rs +++ b/crates/oxide_vm/src/vm/dispatch_loop.rs @@ -202,12 +202,19 @@ impl Vm { return Err("program counter out of bounds".into()); } - let instr = self.bytecode[self.pc]; + let pc = self.pc; + let instr = self.bytecode[pc]; let op = opcode::opcode(instr); let rd = opcode::rd(instr) as usize; let a = opcode::a(instr) as usize; let b = opcode::b(instr) as usize; - self.pc += 1; + self.pc = pc + 1; + + // 逐指令 trace:运行时开关短路(默认关闭,零输出零分配); + // pc 为字节码 word 序号,与反汇编 offset 列同单位。 + if self.trace_instructions { + eprintln!("pc={pc:4} {op:>16} rd={rd} a={a} b={b}"); + } match op { OpCode::NOP => {} diff --git a/crates/oxide_vm/src/vm/mod.rs b/crates/oxide_vm/src/vm/mod.rs index dcd1798a..c1d7cf66 100644 --- a/crates/oxide_vm/src/vm/mod.rs +++ b/crates/oxide_vm/src/vm/mod.rs @@ -350,6 +350,10 @@ pub struct Vm { pub(crate) saved_flat_id_stack: Vec, /// 帧切换时暂存调用方表代际的栈(与 saved_flat_id_stack 同步 push/pop)。 pub(crate) saved_table_gen_stack: Vec, + /// 逐指令 trace 开关:开启时 dispatch 主循环对每条指令向 stderr 写一行 + /// `pc + opcode + 操作数`。运行时开关,旁路 tracing 级别上限(release + /// 构建可用);默认关闭,关闭时热路径仅一次 bool 比较,零输出零分配。 + pub trace_instructions: bool, } impl Drop for Vm { @@ -413,6 +417,12 @@ impl Vm { self.regs[idx as usize] = val; } + /// 设置逐指令 trace 开关(开启后 dispatch 主循环逐指令向 stderr 写 + /// `pc + opcode + 操作数` 行,pc 为字节码 word 序号,与反汇编 offset 同单位)。 + pub fn set_instruction_trace(&mut self, on: bool) { + self.trace_instructions = on; + } + /// 只读访问 VM 的 epoch arena。 pub fn epoch(&self) -> &Epoch { &self.epoch diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index 4745e673..c035b0fe 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -157,6 +157,7 @@ impl Vm { active_table_gen: 0, saved_flat_id_stack: Vec::new(), saved_table_gen_stack: Vec::new(), + trace_instructions: false, }; vm.init_generator_intrinsics(); vm.init_promise_intrinsics(); @@ -291,6 +292,7 @@ impl Vm { active_table_gen: 0, saved_flat_id_stack: Vec::new(), saved_table_gen_stack: Vec::new(), + trace_instructions: false, }; vm.init_generator_intrinsics(); vm.init_promise_intrinsics();