diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dc1d7c16..184830d91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ Enhancements * Rhai Grain VM is now at par or faster than the AST interpreter for scripts with callbacks ([`#1159`](https://github.com/rhaiscript/rhai/pull/1159)). * `rhai-run` now supports loading and executing Rhai Grain bytecodes if the `grain` feature is enabled ([`#1160`](https://github.com/rhaiscript/rhai/pull/1160)). * The example `grain_dump` is now split into two CLI tools in `bin`: `grain-compile`, which compiles a Rhai script into Rhai Grain bytecodes, and `grain-dump` which dissembles a Rhai Grain bytecodes files ([`#1160`](https://github.com/rhaiscript/rhai/pull/1160)). +* `grain-dump` now disassembles Rhai Grain bytecodes files with more complete information ([`#1168`](https://github.com/rhaiscript/rhai/pull/1168)). Version 1.26.0 diff --git a/src/bin/grain-dump.rs b/src/bin/grain-dump.rs index 5c87ac218..03bb5486f 100644 --- a/src/bin/grain-dump.rs +++ b/src/bin/grain-dump.rs @@ -18,7 +18,7 @@ fn dump(program: &Program, code: &[u8], name: &str, chunk: &rhai::grain::bytecod (Some(line), Some(col)) => format!("{line}:{col}"), _ => String::new(), }; - println!(" {at:>5} {:<8} {op:?}", where_); + println!(" {at:>5} {where_:<8} {}", op.disassemble(program)); } } @@ -35,7 +35,12 @@ fn main() -> Result<(), Box> { dump(&program, code, "main", program.main()); for f in program.functions() { - let label = format!("fn #{} ({} params)", f.name, f.params.len()); + let label = format!( + "fn {} : #{} ({} params)", + f.disassemble(&program), + f.name, + f.params.len(), + ); dump(&program, code, &label, &f.chunk); } diff --git a/src/grain/bytecode/chain.rs b/src/grain/bytecode/chain.rs index 5b02ce457..fdc2b665e 100644 --- a/src/grain/bytecode/chain.rs +++ b/src/grain/bytecode/chain.rs @@ -1,8 +1,10 @@ -#[cfg(feature = "no_std")] -use std::prelude::v1::*; +use crate::grain::Program; use bitflags::bitflags; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + bitflags! { /// Per-step flags for a chain. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -108,6 +110,57 @@ impl Step { Step::Property { .. } | Step::Method { .. } => 1, } } + + /// Dump the disassembly of the step. + pub fn disassemble(&self, program: &Program) -> String { + match self { + Step::Index { operand, flags, .. } => { + format!( + "{}[{operand}]", + if flags.contains(StepFlags::SKIP_IF_UNIT) { + "?" + } else { + "" + } + ) + } + Step::Property { name, flags, .. } => { + format!( + ".{}{}", + if flags.contains(StepFlags::SKIP_IF_UNIT) { + "?" + } else { + "" + }, + program.name(*name).unwrap() + ) + } + Step::Method { + name, + argc, + operand, + flags, + .. + } if *argc > 0 => format!( + ".{}{}({argc} args from {operand})", + if flags.contains(StepFlags::SKIP_IF_UNIT) { + "?" + } else { + "" + }, + program.name(*name).unwrap() + ), + Step::Method { name, flags, .. } => format!( + ".{}{}()", + if flags.contains(StepFlags::SKIP_IF_UNIT) { + "?" + } else { + "" + }, + program.name(*name).unwrap() + ), + } + } } /// What a chain does when it gets to the end. @@ -123,6 +176,19 @@ pub enum Tail { }, } +impl Tail { + /// Dump the disassembly of the tail. + pub fn disassemble(&self, program: &Program) -> String { + match self { + Tail::Read => String::new(), + Tail::Assign { op } => match op { + Some(op) => format!("{}", program.assign_op(*op).unwrap().disassemble(program)), + None => "=".to_string(), + }, + } + } +} + /// Where a chain starts. /// /// The distinction is whether the root has an identity to write back into. @@ -333,4 +399,23 @@ impl Chain { .iter() .any(|step| matches!(step, Step::Method { .. })) } + + /// Dump the disassembly of the entire chain. + pub fn disassemble(&self, program: &Program) -> String { + format!( + "{:?} {} {} {}", + self.root, + match self.root { + Root::Named { name, .. } | Root::Local { name, .. } => program.name(name).unwrap(), + Root::This { .. } => "this", + Root::Temporary => "", + }, + self.steps + .iter() + .map(|step| step.disassemble(program)) + .collect::>() + .join(" "), + self.tail.disassemble(program) + ) + } } diff --git a/src/grain/bytecode/op.rs b/src/grain/bytecode/op.rs index 668c7c34a..8346bba07 100644 --- a/src/grain/bytecode/op.rs +++ b/src/grain/bytecode/op.rs @@ -1,4 +1,7 @@ +use crate::grain::Program; use crate::types::Token; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; /// What `x op= y` needs to reproduce Rhai's resolution order. /// @@ -22,6 +25,19 @@ pub struct AssignOp { pub op_name: u32, } +impl AssignOp { + /// Dump the disassembly of the operation. + pub fn disassemble(&self, program: &Program) -> String { + format!( + "{} ({:?}) / {} ({:?})", + program.name(self.op_assign_name).unwrap(), + self.op_assign, + program.name(self.op_name).unwrap(), + self.op, + ) + } +} + /// Where [`Op::CallRef`] finds the variable it calls through. /// /// The two differ in how the variable is reached, not in what happens to it: @@ -643,3 +659,79 @@ pub enum Op { /// End the chunk, yielding the top of the operand stack, or unit if empty. Return, } + +impl Op { + /// Dump the disassembly of the operation. + pub fn disassemble(&self, program: &Program) -> String { + match self { + Op::Const(idx) => format!("{self:?} = {}", program.constant(*idx).unwrap()), + Op::LoadNamed(name) => format!("{self:?} : {}", program.name(*name).unwrap()), + Op::AssignNamed { name, op } => { + if let Some(op) = op { + format!( + "{self:?} : {} {}", + program.name(*name).unwrap(), + program.assign_op(*op).unwrap().disassemble(program) + ) + } else { + format!("{self:?} : {}", program.name(*name).unwrap(),) + } + } + + Op::AssignLocal { var_name, op, .. } => { + if let Some(op) = op { + format!( + "{self:?} : {} {}", + program.name(*var_name).unwrap(), + program.assign_op(*op).unwrap().disassemble(program) + ) + } else { + format!("{self:?} : {}", program.name(*var_name).unwrap(),) + } + } + Op::DeclareLocal { name, is_const } => { + format!( + "{self:?} : {} {}", + if *is_const { "const" } else { "let" }, + program.name(*name).unwrap() + ) + } + Op::Call { name, op, .. } => { + if let Some(op) = op { + format!( + "{self:?} : {} ({:?})", + program.name(*name).unwrap(), + program.token(*op).unwrap() + ) + } else { + format!("{self:?} : {}", program.name(*name).unwrap(),) + } + } + + Op::CallRef { name, .. } => format!("{self:?} : {}", program.name(*name).unwrap(),), + Op::Switch(idx) => format!( + "Switch({idx}) {}", + program.switch(*idx).unwrap().disassemble(program), + ), + Op::ShareNamed(name) => format!("{self:?} : {}", program.name(*name).unwrap()), + Op::LoadSharedNamed(name) => format!("{self:?} : {}", program.name(*name).unwrap()), + Op::AssignThis { op, .. } if op.is_some() => { + format!( + "{self:?} : this {:?}", + program.assign_op(op.unwrap()).unwrap().disassemble(program) + ) + } + Op::MakeClosure(name) => format!("{self:?} : {}", program.name(*name).unwrap(),), + Op::Chain(idx) => { + let chain = program.chain(*idx).unwrap(); + format!( + "Chain({idx}, {} total operands): {}", + chain.operands, + chain.disassemble(program), + ) + } + + _ => format!("{self:?}"), + } + } +} diff --git a/src/grain/bytecode/switch.rs b/src/grain/bytecode/switch.rs index 28dcd1294..355069b42 100644 --- a/src/grain/bytecode/switch.rs +++ b/src/grain/bytecode/switch.rs @@ -1,4 +1,5 @@ use crate::func::{calc_switch_value_hash, StraightHashMap}; +use crate::grain::Program; use crate::{eval::RangeCase, Dynamic, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -25,6 +26,26 @@ pub struct Switch { pub default: u32, } +impl Switch { + /// Dump the disassembly of the operation. + pub fn disassemble(&self, program: &Program) -> String { + format!( + "{{ {} }}", + self.cases + .iter() + .flat_map(|cases| cases.iter()) + .map(|(_, (target, value))| format!( + "case {} => {target}", + program.constant(*value).unwrap() + )) + .chain(self.ranges.iter().map(|r| r.disassemble())) + .chain(std::iter::once(format!("default => {}", self.default))) + .collect::>() + .join(", ") + ) + } +} + /// One `a..b => ...` arm. #[derive(Debug, Clone, PartialEq, Eq)] pub struct SwitchRange { @@ -39,8 +60,18 @@ pub struct SwitchRange { } impl SwitchRange { - /// Whether a subject falls in this range. - /// + /// Dump the disassembly of the switch range. + pub fn disassemble(&self) -> String { + if self.inclusive { + format!("range {}..={} => {}", self.from, self.to, self.target) + } else { + format!("range {}..{} => {}", self.from, self.to, self.target) + } + } +} + +impl SwitchRange { + /// Whether a subject falls in this range. /// /// Delegates to Rhai's own `RangeCase` rather than comparing integers, /// because a range arm matches more than integers: `switch 5.5 { 0..10 => /// .. }` matches, and under the `decimal` feature so does a `Decimal` diff --git a/src/grain/program.rs b/src/grain/program.rs index 33dff7098..19294aa2f 100644 --- a/src/grain/program.rs +++ b/src/grain/program.rs @@ -52,6 +52,22 @@ pub struct Function { pub chunk: Chunk, } +impl Function { + pub fn disassemble(&self, program: &Program) -> String { + format!( + "{}{}{}({})", + self.this_type.map_or("", |t| program.name(t).unwrap()), + if self.this_type.is_some() { "!" } else { "" }, + program.name(self.name).unwrap(), + self.params + .iter() + .map(|&p| program.name(p).unwrap()) + .collect::>() + .join(", "), + ) + } +} + /// A compiled script, ready to run against an `Engine`. /// /// Owns everything execution needs that is not the `Engine` itself, so the