diff --git a/src/bit_encoding/bititer.rs b/src/bit_encoding/bititer.rs index 1a61220e..ba85378c 100644 --- a/src/bit_encoding/bititer.rs +++ b/src/bit_encoding/bititer.rs @@ -194,11 +194,29 @@ impl> Iterator for BitIter { self.next() } } + + fn size_hint(&self) -> (usize, Option) { + let (lo, hi) = self.iter.size_hint(); + let adj = |n| 8 - self.read_bits + 8 * n; + (adj(lo), hi.map(adj)) + } +} + +impl core::iter::FusedIterator for BitIter where + I: Iterator + core::iter::FusedIterator +{ +} + +impl core::iter::ExactSizeIterator for BitIter where + I: Iterator + core::iter::ExactSizeIterator +{ } impl<'a> BitIter>> { - /// Creates a new bitwise iterator from a bytewise one. Equivalent - /// to using `From` + /// Creates a new bitwise iterator from a bytewise one. + /// + /// Takes start and end indices *in bits*. If you want to use the entire slice, + /// `BitIter::from` is equivalent and easier to call. pub fn byte_slice_window(sl: &'a [u8], start: usize, end: usize) -> Self { assert!(start <= end); assert!(end <= sl.len() * 8); @@ -423,6 +441,8 @@ mod tests { #[test] fn empty_iter() { let mut iter = BitIter::from([].iter().cloned()); + assert_eq!(iter.len(), 0); + assert!(iter.next().is_none()); assert!(iter.next().is_none()); assert_eq!(iter.read_bit(), Err(EarlyEndOfStreamError)); assert_eq!(iter.read_u2(), Err(EarlyEndOfStreamError)); @@ -434,8 +454,11 @@ mod tests { #[test] fn one_bit_iter() { let mut iter = BitIter::from([0x80].iter().cloned()); + assert_eq!(iter.len(), 8); assert_eq!(iter.read_bit(), Ok(true)); + assert_eq!(iter.len(), 7); assert_eq!(iter.read_bit(), Ok(false)); + assert_eq!(iter.len(), 6); assert_eq!(iter.read_u8(), Err(EarlyEndOfStreamError)); assert_eq!(iter.n_total_read(), 2); } @@ -443,17 +466,22 @@ mod tests { #[test] fn bit_by_bit() { let mut iter = BitIter::from([0x0f, 0xaa].iter().cloned()); + assert_eq!(iter.len(), 16); for _ in 0..4 { assert_eq!(iter.next(), Some(false)); } + assert_eq!(iter.len(), 12); for _ in 0..4 { assert_eq!(iter.next(), Some(true)); } + assert_eq!(iter.len(), 8); for _ in 0..4 { assert_eq!(iter.next(), Some(true)); assert_eq!(iter.next(), Some(false)); } + assert_eq!(iter.len(), 0); assert_eq!(iter.next(), None); + assert_eq!(iter.len(), 0); } #[test] @@ -480,7 +508,9 @@ mod tests { let data = [0x12, 0x23, 0x34]; let mut full = BitIter::byte_slice_window(&data, 0, 24); + assert_eq!(full.len(), 24); assert_eq!(full.read_u8(), Ok(0x12)); + assert_eq!(full.len(), 16); assert_eq!(full.n_total_read(), 8); assert_eq!(full.read_u8(), Ok(0x23)); assert_eq!(full.n_total_read(), 16); @@ -489,7 +519,9 @@ mod tests { assert_eq!(full.read_u8(), Err(EarlyEndOfStreamError)); let mut mid = BitIter::byte_slice_window(&data, 8, 16); + assert_eq!(mid.len(), 8); assert_eq!(mid.read_u8(), Ok(0x23)); + assert_eq!(mid.len(), 0); assert_eq!(mid.read_u8(), Err(EarlyEndOfStreamError)); let mut offs = BitIter::byte_slice_window(&data, 4, 20); @@ -498,13 +530,21 @@ mod tests { assert_eq!(offs.read_u8(), Err(EarlyEndOfStreamError)); let mut shift1 = BitIter::byte_slice_window(&data, 1, 24); + assert_eq!(shift1.len(), 23); assert_eq!(shift1.read_u8(), Ok(0x24)); + assert_eq!(shift1.len(), 15); assert_eq!(shift1.read_u8(), Ok(0x46)); + assert_eq!(shift1.len(), 7); assert_eq!(shift1.read_u8(), Err(EarlyEndOfStreamError)); + assert_eq!(shift1.len(), 7); let mut shift7 = BitIter::byte_slice_window(&data, 7, 24); + assert_eq!(shift7.len(), 17); assert_eq!(shift7.read_u8(), Ok(0x11)); + assert_eq!(shift7.len(), 9); assert_eq!(shift7.read_u8(), Ok(0x9a)); + assert_eq!(shift7.len(), 1); assert_eq!(shift7.read_u8(), Err(EarlyEndOfStreamError)); + assert_eq!(shift7.len(), 1); } } diff --git a/src/bit_machine/frame.rs b/src/bit_machine/frame.rs index ba20e758..9415b9ac 100644 --- a/src/bit_machine/frame.rs +++ b/src/bit_machine/frame.rs @@ -34,15 +34,28 @@ impl Frame { } /// Return the start index of the frame inside the referenced data. - pub fn start(&self) -> usize { + pub(super) fn start(&self) -> usize { self.start } /// Return the bit width of the frame. - pub fn bit_width(&self) -> usize { + pub(super) fn bit_width(&self) -> usize { self.len } + /// Makes a copy of the frame. + /// + /// This copies *only the indices* and none of the underlying + /// data. It is the caller's responsibility to make sure that + /// the indices are not invalidated. + pub(super) fn shallow_copy(&self) -> Self { + Self { + cursor: self.cursor, + start: self.start, + len: self.len, + } + } + /// Reset the cursor to the start. pub(super) fn reset_cursor(&mut self) { self.cursor = self.start; @@ -104,7 +117,10 @@ impl Frame { /// Extend the present frame with a read-only reference the the data /// and return the resulting struct. - pub fn as_bit_iter<'a>(&self, data: &'a [u8]) -> BitIter + 'a> { + pub(super) fn as_bit_iter<'a>( + &self, + data: &'a [u8], + ) -> BitIter>> { BitIter::byte_slice_window(data, self.start, self.start + self.len) } } diff --git a/src/bit_machine/mod.rs b/src/bit_machine/mod.rs index 05625489..39c7b35e 100644 --- a/src/bit_machine/mod.rs +++ b/src/bit_machine/mod.rs @@ -8,21 +8,26 @@ mod frame; mod limits; +mod tracker; -use std::collections::HashSet; use std::error; use std::fmt; use std::sync::Arc; +use crate::analysis; use crate::jet::{Jet, JetFailed}; use crate::node::{self, RedeemNode}; use crate::types::Final; -use crate::{analysis, Ihr}; use crate::{Cmr, FailEntropy, Value}; use frame::Frame; -use simplicity_sys::ffi::UWORD; pub use self::limits::LimitError; +pub use self::tracker::{ + ExecTracker, NoTracker, NodeOutput, PruneTracker, SetTracker, StderrTracker, +}; + +/// An iterator over the contents of a read or write frame which yields bits. +pub type FrameIter<'a> = crate::BitIter>>; /// An execution context for a Simplicity program pub struct BitMachine { @@ -71,7 +76,7 @@ impl BitMachine { } /// Push a new frame of given size onto the write frame stack - fn new_frame(&mut self, len: usize) { + fn new_write_frame(&mut self, len: usize) { debug_assert!( self.next_frame_start + len <= self.data.len() * 8, "Data out of bounds: number of cells" @@ -86,14 +91,14 @@ impl BitMachine { } /// Move the active write frame to the read frame stack - fn move_frame(&mut self) { + fn move_write_frame_to_read(&mut self) { let mut _active_write_frame = self.write.pop().unwrap(); _active_write_frame.reset_cursor(); self.read.push(_active_write_frame); } /// Drop the active read frame - fn drop_frame(&mut self) { + fn drop_read_frame(&mut self) { let active_read_frame = self.read.pop().unwrap(); self.next_frame_start -= active_read_frame.bit_width(); assert_eq!(self.next_frame_start, active_read_frame.start()); @@ -203,9 +208,9 @@ impl BitMachine { } // Unit value doesn't need extra frame if !input.is_empty() { - self.new_frame(input.padded_len()); + self.new_write_frame(input.padded_len()); self.write_value(input); - self.move_frame(); + self.move_write_frame_to_read(); } Ok(()) } @@ -223,28 +228,11 @@ impl BitMachine { self.exec_with_tracker(program, env, &mut NoTracker) } - /// Execute the given `program` on the Bit Machine and track executed case branches. - /// - /// If the program runs successfully, then two sets of IHRs are returned: - /// - /// 1) The IHRs of case nodes whose _left_ branch was executed. - /// 2) The IHRs of case nodes whose _right_ branch was executed. - /// - /// ## Precondition - /// - /// The Bit Machine is constructed via [`Self::for_program()`] to ensure enough space. - pub(crate) fn exec_prune( - &mut self, - program: &RedeemNode, - env: &J::Environment, - ) -> Result { - let mut tracker = SetTracker::default(); - self.exec_with_tracker(program, env, &mut tracker)?; - Ok(tracker) - } - /// Execute the given `program` on the Bit Machine, using the given environment and tracker. /// + /// See [`crate::bit_machine::StderrTracker`] as an example which outputs various debug + /// data for each node, providing a track of the bit machine's operation. + /// /// ## Precondition /// /// The Bit Machine is constructed via [`Self::for_program()`] to ensure enough space. @@ -256,8 +244,8 @@ impl BitMachine { ) -> Result { enum CallStack<'a, J: Jet> { Goto(&'a RedeemNode), - MoveFrame, - DropFrame, + MoveWriteFrameToRead, + DropReadFrame, CopyFwd(usize), Back(usize), } @@ -267,8 +255,8 @@ impl BitMachine { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { CallStack::Goto(ins) => write!(f, "goto {}", ins.inner()), - CallStack::MoveFrame => f.write_str("move frame"), - CallStack::DropFrame => f.write_str("drop frame"), + CallStack::MoveWriteFrameToRead => f.write_str("move frame"), + CallStack::DropReadFrame => f.write_str("drop frame"), CallStack::CopyFwd(n) => write!(f, "copy/fwd {}", n), CallStack::Back(n) => write!(f, "back {}", n), } @@ -284,10 +272,14 @@ impl BitMachine { let output_width = ip.arrow().target.bit_width(); if output_width > 0 { - self.new_frame(output_width); + self.new_write_frame(output_width); } 'main_loop: loop { + // Make a copy of the input frame to give to the tracker. + let input_frame = self.read.last().map(Frame::shallow_copy); + let mut jet_result = Ok(()); + match ip.inner() { node::Inner::Unit => {} node::Inner::Iden => { @@ -313,10 +305,10 @@ impl BitMachine { node::Inner::Comp(left, right) => { let size_b = left.arrow().target.bit_width(); - self.new_frame(size_b); - call_stack.push(CallStack::DropFrame); + self.new_write_frame(size_b); + call_stack.push(CallStack::DropReadFrame); call_stack.push(CallStack::Goto(right)); - call_stack.push(CallStack::MoveFrame); + call_stack.push(CallStack::MoveWriteFrameToRead); call_stack.push(CallStack::Goto(left)); } node::Inner::Disconnect(left, right) => { @@ -325,18 +317,18 @@ impl BitMachine { let size_prod_b_c = left.arrow().target.bit_width(); let size_b = size_prod_b_c - right.arrow().source.bit_width(); - self.new_frame(size_prod_256_a); + self.new_write_frame(size_prod_256_a); self.write_bytes(right.cmr().as_ref()); self.copy(size_a); - self.move_frame(); - self.new_frame(size_prod_b_c); + self.move_write_frame_to_read(); + self.new_write_frame(size_prod_b_c); // Remember that call stack pushes are executed in reverse order - call_stack.push(CallStack::DropFrame); - call_stack.push(CallStack::DropFrame); + call_stack.push(CallStack::DropReadFrame); + call_stack.push(CallStack::DropReadFrame); call_stack.push(CallStack::Goto(right)); call_stack.push(CallStack::CopyFwd(size_b)); - call_stack.push(CallStack::MoveFrame); + call_stack.push(CallStack::MoveWriteFrameToRead); call_stack.push(CallStack::Goto(left)); } node::Inner::Take(left) => call_stack.push(CallStack::Goto(left)), @@ -353,32 +345,18 @@ impl BitMachine { let (sum_a_b, _c) = ip.arrow().source.as_product().unwrap(); let (a, b) = sum_a_b.as_sum().unwrap(); - if tracker.is_track_debug_enabled() { - if let node::Inner::AssertL(_, cmr) = ip.inner() { - let mut bits = in_frame.as_bit_iter(&self.data); - // Skips 1 + max(a.bit_width, b.bit_width) - a.bit_width - bits.nth(a.pad_left(b)) - .expect("AssertL: unexpected end of frame"); - let value = Value::from_padded_bits(&mut bits, _c) - .expect("AssertL: decode `C` value"); - tracker.track_dbg_call(cmr, value); - } - } - match (ip.inner(), choice_bit) { (node::Inner::Case(_, right), true) | (node::Inner::AssertR(_, right), true) => { self.fwd(1 + a.pad_right(b)); call_stack.push(CallStack::Back(1 + a.pad_right(b))); call_stack.push(CallStack::Goto(right)); - tracker.track_right(ip.ihr()); } (node::Inner::Case(left, _), false) | (node::Inner::AssertL(left, _), false) => { self.fwd(1 + a.pad_left(b)); call_stack.push(CallStack::Back(1 + a.pad_left(b))); call_stack.push(CallStack::Goto(left)); - tracker.track_left(ip.ihr()); } (node::Inner::AssertL(_, r_cmr), true) => { return Err(ExecutionError::ReachedPrunedBranch(*r_cmr)) @@ -390,18 +368,48 @@ impl BitMachine { } } node::Inner::Witness(value) => self.write_value(value), - node::Inner::Jet(jet) => self.exec_jet(*jet, env, tracker)?, + node::Inner::Jet(jet) => { + jet_result = self.exec_jet(*jet, env); + } node::Inner::Word(value) => self.write_value(value.as_value()), node::Inner::Fail(entropy) => { return Err(ExecutionError::ReachedFailNode(*entropy)) } } + // Notify the tracker. + { + // Notice that, because the read frame stack is only ever + // shortened by `drop_read_frame`, and that method was not + // called above, this frame is still valid and correctly + // describes the Bit Machine "input" to the current node, + // no matter the node. + let read_iter = input_frame + .map(|frame| frame.as_bit_iter(&self.data)) + .unwrap_or(crate::BitIter::from([].iter().copied())); + // See the docs on `tracker::NodeOutput` for more information about + // this match. + let output = match (ip.inner(), &jet_result) { + (node::Inner::Unit | node::Inner::Iden | node::Inner::Witness(_), _) + | (node::Inner::Jet(_), Ok(_)) => NodeOutput::Success( + self.write + .last() + .map(|r| r.as_bit_iter(&self.data)) + .unwrap_or(crate::BitIter::from([].iter().copied())), + ), + (node::Inner::Jet(_), Err(_)) => NodeOutput::JetFailed, + _ => NodeOutput::NonTerminal, + }; + tracker.visit_node(ip, read_iter, output); + } + // Fail if the jet failed. + jet_result?; + ip = loop { match call_stack.pop() { Some(CallStack::Goto(next)) => break next, - Some(CallStack::MoveFrame) => self.move_frame(), - Some(CallStack::DropFrame) => self.drop_frame(), + Some(CallStack::MoveWriteFrameToRead) => self.move_write_frame_to_read(), + Some(CallStack::DropReadFrame) => self.drop_read_frame(), Some(CallStack::CopyFwd(n)) => { self.copy(n); self.fwd(n); @@ -427,12 +435,7 @@ impl BitMachine { } } - fn exec_jet>( - &mut self, - jet: J, - env: &J::Environment, - tracker: &mut T, - ) -> Result<(), JetFailed> { + fn exec_jet(&mut self, jet: J, env: &J::Environment) -> Result<(), JetFailed> { use crate::ffi::c_jets::frame_ffi::{c_readBit, c_writeBit, CFrameItem}; use crate::ffi::c_jets::uword_width; use crate::ffi::ffi::UWORD; @@ -518,15 +521,13 @@ impl BitMachine { let output_width = jet.target_ty().to_bit_width(); // Input buffer is implicitly referenced by input read frame! // Same goes for output buffer - let (input_read_frame, input_buffer) = unsafe { get_input_frame(self, input_width) }; + let (input_read_frame, _input_buffer) = unsafe { get_input_frame(self, input_width) }; let (mut output_write_frame, output_buffer) = unsafe { get_output_frame(output_width) }; let jet_fn = jet.c_jet_ptr(); let c_env = J::c_jet_env(env); let success = jet_fn(&mut output_write_frame, input_read_frame, c_env); - tracker.track_jet_call(&jet, &input_buffer, &output_buffer, success); - if !success { Err(JetFailed) } else { @@ -536,92 +537,6 @@ impl BitMachine { } } -/// A type that keeps track of Bit Machine execution. -/// -/// The trait is implemented for [`SetTracker`], that tracks which case branches were executed, -/// and it is implemented for [`NoTracker`], which is a dummy tracker that is -/// optimized out by the compiler. -/// -/// The trait enables us to turn tracking on or off depending on a generic parameter. -pub trait ExecTracker { - /// Track the execution of the left branch of the case node with the given `ihr`. - fn track_left(&mut self, ihr: Ihr); - - /// Track the execution of the right branch of the case node with the given `ihr`. - fn track_right(&mut self, ihr: Ihr); - - /// Track the execution of a `jet` call with the given `input_buffer`, `output_buffer`, and call result `success`. - fn track_jet_call( - &mut self, - jet: &J, - input_buffer: &[UWORD], - output_buffer: &[UWORD], - success: bool, - ); - - /// Track the potential execution of a `dbg!` call with the given `cmr` and `value`. - fn track_dbg_call(&mut self, cmr: &Cmr, value: Value); - - /// Check if tracking debug calls is enabled. - fn is_track_debug_enabled(&self) -> bool; -} - -/// Tracker of executed left and right branches for each case node. -#[derive(Clone, Debug, Default)] -pub struct SetTracker { - left: HashSet, - right: HashSet, -} - -impl SetTracker { - /// Access the set of IHRs of case nodes whose left branch was executed. - pub fn left(&self) -> &HashSet { - &self.left - } - - /// Access the set of IHRs of case nodes whose right branch was executed. - pub fn right(&self) -> &HashSet { - &self.right - } -} - -/// Tracker that does not do anything (noop). -#[derive(Copy, Clone, Debug)] -pub struct NoTracker; - -impl ExecTracker for SetTracker { - fn track_left(&mut self, ihr: Ihr) { - self.left.insert(ihr); - } - - fn track_right(&mut self, ihr: Ihr) { - self.right.insert(ihr); - } - - fn track_jet_call(&mut self, _: &J, _: &[UWORD], _: &[UWORD], _: bool) {} - - fn track_dbg_call(&mut self, _: &Cmr, _: Value) {} - - fn is_track_debug_enabled(&self) -> bool { - false - } -} - -impl ExecTracker for NoTracker { - fn track_left(&mut self, _: Ihr) {} - - fn track_right(&mut self, _: Ihr) {} - - fn track_jet_call(&mut self, _: &J, _: &[UWORD], _: &[UWORD], _: bool) {} - - fn track_dbg_call(&mut self, _: &Cmr, _: Value) {} - - fn is_track_debug_enabled(&self) -> bool { - // Set flag to test frame decoding in unit tests - cfg!(test) - } -} - /// Errors related to simplicity Execution #[derive(Debug)] pub enum ExecutionError { diff --git a/src/bit_machine/tracker.rs b/src/bit_machine/tracker.rs new file mode 100644 index 00000000..0fef2494 --- /dev/null +++ b/src/bit_machine/tracker.rs @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Bit Machine Tracker +//! +//! This module provides traits for adding "trackers" to the bit machine execution +//! and pruning algorithms, which can provide debugging output or control which +//! branches are pruned. It also provides a couple example/utility trackers. +//! +//! It is a private module but all types and traits are re-exported above. + +use std::collections::HashSet; + +use crate::jet::Jet; +use crate::node::Inner; +use crate::{Ihr, RedeemNode, Value}; + +/// Write frame of a terminal (childless) Simplicity program node. +/// +/// When a terminal node of a program is encountered in the Bit Machine, it +/// has a well-defined "output": the contents of the topmost write frame in +/// the machine. In particular, for `witness` nodes this will be the witness +/// data, for jets it will be the result of the jet, and so on. +/// +/// For non-terminal nodes, the Bit Machine typically does some setup, then +/// executes the nodes' children, then does some teardown. So at no point is +/// there a well-defined "output" we can provide. +#[derive(Debug, Clone)] +pub enum NodeOutput<'m> { + /// Non-terminal node, which has no output. + NonTerminal, + /// Node was a jet which failed, i.e. aborted the program, and therefore + /// has no output. + JetFailed, + /// Node succeeded. This is its output frame. + Success(super::FrameIter<'m>), +} + +/// An object which can be used to introspect the execution of the Bit Machine. +/// +/// If this tracker records accesses to the left and right children of `Case` nodes, you +/// may want to also implement [`PruneTracker`] so that this data can be used by +/// [`RedeemNode::prune_with_tracker`] to prune the program. The most straightforward +/// way to do this is to embed a [`SetTracker`] in your tracker and forward all the trait +/// methods to that. +pub trait ExecTracker { + /// Called immediately after a specific node of the program is executed, but before + /// its children are executed. + /// + /// More precisely, this iterates through the through the Simplicity program tree in + /// *pre* ordering. That is, for the program `comp iden unit` the nodes will be visited + /// in the order `comp`, `iden`, `unit`. + /// + /// This method can be used for logging, to track left or right accesses of the children of a + /// `Case` node (to do this, call `input.peek_bit()`; false means left and true means right), + /// to extract debug information (which may be embedded in the hidden CMR in `AssertL` + /// and `AssertR` nodes, depending how the program was constructed), and so on. + /// + /// The provided arguments are: + /// * `node` is the node which was just visited. + /// * `input` is an iterator over the read frame when the node's execution began + /// * for terminal nodes (`witness`, `unit`, `iden` and jets), `output` is an iterator + /// the write frame after the node has executed. See [`NodeOutput`] for more information. + fn visit_node(&mut self, _node: &RedeemNode, _input: super::FrameIter, _output: NodeOutput) { + } +} + +pub trait PruneTracker: ExecTracker { + /// Returns true if the left branch of the of the `Case` node with the IHR `ihr` was taken. + fn contains_left(&self, ihr: Ihr) -> bool; + + /// Returns true if the right branch of the of the `Case` node with the IHR `ihr` was taken. + fn contains_right(&self, ihr: Ihr) -> bool; +} + +/// Tracker of executed left and right branches for each case node. +#[derive(Clone, Debug, Default)] +pub struct SetTracker { + left: HashSet, + right: HashSet, +} + +impl ExecTracker for SetTracker { + fn visit_node<'d>( + &mut self, + node: &RedeemNode, + mut input: super::FrameIter, + _output: NodeOutput, + ) { + match (node.inner(), input.next()) { + (Inner::AssertL(..) | Inner::Case(..), Some(false)) => { + self.left.insert(node.ihr()); + } + (Inner::AssertR(..) | Inner::Case(..), Some(true)) => { + self.right.insert(node.ihr()); + } + _ => {} + } + } +} + +impl PruneTracker for SetTracker { + fn contains_left(&self, ihr: Ihr) -> bool { + self.left.contains(&ihr) + } + + fn contains_right(&self, ihr: Ihr) -> bool { + self.right.contains(&ihr) + } +} + +/// Tracker that does not do anything (noop). +#[derive(Copy, Clone, Debug)] +pub struct NoTracker; + +impl ExecTracker for NoTracker { + fn visit_node<'d>( + &mut self, + node: &RedeemNode, + mut input: super::FrameIter, + output: NodeOutput, + ) { + if cfg!(test) { + // In unit tests, attempt to decode values from the frames, confirming that + // decoding works. + Value::from_padded_bits(&mut input, &node.arrow().source) + .expect("decoding input should work"); + if let NodeOutput::Success(mut output) = output { + Value::from_padded_bits(&mut output, &node.arrow().target) + .expect("decoding output should work"); + } + } + } +} + +/// Tracker that just outputs all its activity to stderr. +#[derive(Clone, Debug, Default)] +pub struct StderrTracker { + exec_count: usize, + inner: SetTracker, +} + +impl StderrTracker { + /// Constructs a new empty [`StderrTracker`], ready for use. + pub fn new() -> Self { + Self::default() + } +} + +impl ExecTracker for StderrTracker { + fn visit_node(&mut self, node: &RedeemNode, input: super::FrameIter, output: NodeOutput) { + let input_val = Value::from_padded_bits(&mut input.clone(), &node.arrow().source) + .expect("input from bit machine will always be well-formed"); + eprintln!( + "[{:4}] exec {:10} {}", + self.exec_count, + node.inner(), + node.arrow() + ); + eprintln!(" input {input_val}"); + match output.clone() { + NodeOutput::NonTerminal => { /* don't bother describing non-terminal output */ } + NodeOutput::JetFailed => eprintln!(" JET FAILED"), + NodeOutput::Success(mut output) => { + let output_val = Value::from_padded_bits(&mut output, &node.arrow().target) + .expect("output from bit machine will always be well-formed"); + eprintln!(" output {output_val}"); + } + } + + if let crate::node::Inner::AssertL(_, cmr) = node.inner() { + // SimplicityHL, when compiling in "debug mode", tags nodes by inserting + // synthetic AssertL nodes where the "cmr" is actually a key into a lookup + // table of debug information. An implementation of ExecTracker within + // the compiler itself might do a lookup here to output more useful + // information to the user. + eprintln!(" [debug] assertL CMR {cmr}"); + } + + ExecTracker::::visit_node(&mut self.inner, node, input, output); + self.exec_count += 1; + eprintln!(); + } +} + +impl PruneTracker for StderrTracker { + fn contains_left(&self, ihr: Ihr) -> bool { + if PruneTracker::::contains_left(&self.inner, ihr) { + true + } else { + eprintln!("Pruning unexecuted left child of IHR {ihr}"); + false + } + } + + fn contains_right(&self, ihr: Ihr) -> bool { + if PruneTracker::::contains_right(&self.inner, ihr) { + true + } else { + eprintln!("Pruning unexecuted right child of IHR {ihr}"); + false + } + } +} diff --git a/src/node/redeem.rs b/src/node/redeem.rs index df7af5a0..aa0bf45e 100644 --- a/src/node/redeem.rs +++ b/src/node/redeem.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: CC0-1.0 use crate::analysis::NodeBounds; -use crate::bit_machine::{ExecutionError, SetTracker}; +use crate::bit_machine::{ExecutionError, PruneTracker, SetTracker}; use crate::dag::{DagLike, InternalSharing, MaxSharing, PostOrderIterItem}; use crate::jet::Jet; use crate::types::{self, arrow::FinalArrow}; @@ -290,13 +290,30 @@ impl RedeemNode { /// In this case, the witness data needs to be revised. /// The other pruning steps (2 & 3) never fail. pub fn prune(&self, env: &J::Environment) -> Result>, ExecutionError> { - struct Pruner<'brand, J> { + self.prune_with_tracker(env, &mut SetTracker::default()) + } + + /// Prune the redeem program, as in [`Self::prune`], but with a custom tracker which + /// can introspect or control pruning. + /// + /// See [`crate::bit_machine::StderrTracker`] as an example which outputs the IHR of + /// each case combinator that we prune a child of. + pub fn prune_with_tracker>( + &self, + env: &J::Environment, + tracker: &mut T, + ) -> Result>, ExecutionError> { + struct Pruner<'brand, 't, J, T> { inference_context: types::Context<'brand>, - tracker: SetTracker, + tracker: &'t mut T, phantom: PhantomData, } - impl<'brand, J: Jet> Converter, Construct<'brand, J>> for Pruner<'brand, J> { + impl<'brand, 't, J, T> Converter, Construct<'brand, J>> for Pruner<'brand, 't, J, T> + where + J: Jet, + T: PruneTracker, + { type Error = std::convert::Infallible; fn convert_witness( @@ -332,8 +349,8 @@ impl RedeemNode { // but the Converter trait gives us access to the unpruned node (`data`). // The Bit Machine tracked (un)used case branches based on the unpruned IHR. match ( - self.tracker.left().contains(&data.node.ihr()), - self.tracker.right().contains(&data.node.ihr()), + self.tracker.contains_left(data.node.ihr()), + self.tracker.contains_right(data.node.ihr()), ) { (true, true) => Ok(Hide::Neither), (false, true) => Ok(Hide::Left), @@ -418,7 +435,7 @@ impl RedeemNode { // 1) Run the Bit Machine and mark (un)used branches. // This is the only fallible step in the pruning process. let mut mac = BitMachine::for_program(self)?; - let tracker = mac.exec_prune(self, env)?; + mac.exec_with_tracker(self, env, tracker)?; // 2) Prune out unused case branches. // Because the types of the pruned program may change,