Skip to main content

simplicity/bit_machine/
tracker.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! Bit Machine Tracker
4//!
5//! This module provides traits for adding "trackers" to the bit machine execution
6//! and pruning algorithms, which can provide debugging output or control which
7//! branches are pruned. It also provides a couple example/utility trackers.
8//!
9//! It is a private module but all types and traits are re-exported above.
10
11use std::collections::HashSet;
12
13use crate::node::Inner;
14use crate::{Ihr, RedeemNode, Value};
15
16/// Write frame of a terminal (childless) Simplicity program node.
17///
18/// When a terminal node of a program is encountered in the Bit Machine, it
19/// has a well-defined "output": the contents of the topmost write frame in
20/// the machine. In particular, for `witness` nodes this will be the witness
21/// data, for jets it will be the result of the jet, and so on.
22///
23/// For non-terminal nodes, the Bit Machine typically does some setup, then
24/// executes the nodes' children, then does some teardown. So at no point is
25/// there a well-defined "output" we can provide.
26#[derive(Debug, Clone)]
27pub enum NodeOutput<'m> {
28    /// Non-terminal node, which has no output.
29    NonTerminal,
30    /// Node was a jet which failed, i.e. aborted the program, and therefore
31    /// has no output.
32    JetFailed,
33    /// Node succeeded. This is its output frame.
34    Success(super::FrameIter<'m>),
35}
36
37/// An object which can be used to introspect the execution of the Bit Machine.
38///
39/// If this tracker records accesses to the left and right children of `Case` nodes, you
40/// may want to also implement [`PruneTracker`] so that this data can be used by
41/// [`RedeemNode::prune_with_tracker`] to prune the program. The most straightforward
42/// way to do this is to embed a [`SetTracker`] in your tracker and forward all the trait
43/// methods to that.
44pub trait ExecTracker {
45    /// Called immediately after a specific node of the program is executed, but before
46    /// its children are executed.
47    ///
48    /// More precisely, this iterates through the through the Simplicity program tree in
49    /// *pre* ordering. That is, for the program `comp iden unit` the nodes will be visited
50    /// in the order `comp`, `iden`, `unit`.
51    ///
52    /// This method can be used for logging, to track left or right accesses of the children of a
53    /// `Case` node (to do this, call `input.peek_bit()`; false means left and true means right),
54    /// to extract debug information (which may be embedded in the hidden CMR in `AssertL`
55    /// and `AssertR` nodes, depending how the program was constructed), and so on.
56    ///
57    /// The provided arguments are:
58    ///   * `node` is the node which was just visited.
59    ///   * `input` is an iterator over the read frame when the node's execution began
60    ///   * for terminal nodes (`witness`, `unit`, `iden` and jets), `output` is an iterator
61    ///     the write frame after the node has executed. See [`NodeOutput`] for more information.
62    fn visit_node(&mut self, _node: &RedeemNode, _input: super::FrameIter, _output: NodeOutput) {}
63}
64
65pub trait PruneTracker: ExecTracker {
66    /// Returns true if the left branch of the of the `Case` node with the IHR `ihr` was taken.
67    fn contains_left(&self, ihr: Ihr) -> bool;
68
69    /// Returns true if the right branch of the of the `Case` node with the IHR `ihr` was taken.
70    fn contains_right(&self, ihr: Ihr) -> bool;
71}
72
73/// Tracker of executed left and right branches for each case node.
74#[derive(Clone, Debug, Default)]
75pub struct SetTracker {
76    left: HashSet<Ihr>,
77    right: HashSet<Ihr>,
78}
79
80impl ExecTracker for SetTracker {
81    fn visit_node<'d>(
82        &mut self,
83        node: &RedeemNode,
84        mut input: super::FrameIter,
85        _output: NodeOutput,
86    ) {
87        match (node.inner(), input.next()) {
88            (Inner::AssertL(..) | Inner::Case(..), Some(false)) => {
89                self.left.insert(node.ihr());
90            }
91            (Inner::AssertR(..) | Inner::Case(..), Some(true)) => {
92                self.right.insert(node.ihr());
93            }
94            _ => {}
95        }
96    }
97}
98
99impl PruneTracker for SetTracker {
100    fn contains_left(&self, ihr: Ihr) -> bool {
101        self.left.contains(&ihr)
102    }
103
104    fn contains_right(&self, ihr: Ihr) -> bool {
105        self.right.contains(&ihr)
106    }
107}
108
109/// Tracker that does not do anything (noop).
110#[derive(Copy, Clone, Debug)]
111pub struct NoTracker;
112
113impl ExecTracker for NoTracker {
114    fn visit_node<'d>(
115        &mut self,
116        node: &RedeemNode,
117        mut input: super::FrameIter,
118        output: NodeOutput,
119    ) {
120        if cfg!(test) {
121            // In unit tests, attempt to decode values from the frames, confirming that
122            // decoding works.
123            Value::from_padded_bits(&mut input, &node.arrow().source)
124                .expect("decoding input should work");
125            if let NodeOutput::Success(mut output) = output {
126                Value::from_padded_bits(&mut output, &node.arrow().target)
127                    .expect("decoding output should work");
128            }
129        }
130    }
131}
132
133/// Tracker that just outputs all its activity to stderr.
134#[derive(Clone, Debug, Default)]
135pub struct StderrTracker {
136    exec_count: usize,
137    inner: SetTracker,
138}
139
140impl StderrTracker {
141    /// Constructs a new empty [`StderrTracker`], ready for use.
142    pub fn new() -> Self {
143        Self::default()
144    }
145}
146
147impl ExecTracker for StderrTracker {
148    fn visit_node(&mut self, node: &RedeemNode, input: super::FrameIter, output: NodeOutput) {
149        let input_val = Value::from_padded_bits(&mut input.clone(), &node.arrow().source)
150            .expect("input from bit machine will always be well-formed");
151        eprintln!(
152            "[{:4}] exec {:10} {}",
153            self.exec_count,
154            node.inner(),
155            node.arrow()
156        );
157        eprintln!("       input {input_val}");
158        match output.clone() {
159            NodeOutput::NonTerminal => { /* don't bother describing non-terminal output */ }
160            NodeOutput::JetFailed => eprintln!("    JET FAILED"),
161            NodeOutput::Success(mut output) => {
162                let output_val = Value::from_padded_bits(&mut output, &node.arrow().target)
163                    .expect("output from bit machine will always be well-formed");
164                eprintln!("       output {output_val}");
165            }
166        }
167
168        if let crate::node::Inner::AssertL(_, cmr) = node.inner() {
169            // SimplicityHL, when compiling in "debug mode", tags nodes by inserting
170            // synthetic AssertL nodes where the "cmr" is actually a key into a lookup
171            // table of debug information. An implementation of ExecTracker within
172            // the compiler itself might do a lookup here to output more useful
173            // information to the user.
174            eprintln!("      [debug] assertL CMR {cmr}");
175        }
176
177        ExecTracker::visit_node(&mut self.inner, node, input, output);
178        self.exec_count += 1;
179        eprintln!();
180    }
181}
182
183impl PruneTracker for StderrTracker {
184    fn contains_left(&self, ihr: Ihr) -> bool {
185        if PruneTracker::contains_left(&self.inner, ihr) {
186            true
187        } else {
188            eprintln!("Pruning unexecuted left child of IHR {ihr}");
189            false
190        }
191    }
192
193    fn contains_right(&self, ihr: Ihr) -> bool {
194        if PruneTracker::contains_right(&self.inner, ihr) {
195            true
196        } else {
197            eprintln!("Pruning unexecuted right child of IHR {ihr}");
198            false
199        }
200    }
201}