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