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}