Skip to content

Commit 6013bd5

Browse files
committed
bit_machine: replace ExecTracker API with a much more general one
This replaces the `ExecTracker` API with one which is able to detect every node, not just cases and jets; which is able to read the input value for every node (as a bit iterator which can be converted to a value with Value::from_padded_bits) and the output value for terminal nodes; and which can do all the existing things that the tracker can do. I suspect we want to add some examples or unit tests, in particular around "debug nodes". Fixes #324
1 parent 4134976 commit 6013bd5

3 files changed

Lines changed: 129 additions & 77 deletions

File tree

src/bit_machine/frame.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,19 @@ impl Frame {
4343
self.len
4444
}
4545

46+
/// Makes a copy of the frame.
47+
///
48+
/// This copies *only the indices* and none of the underlying
49+
/// data. It is the caller's responsibility to make sure that
50+
/// the indices are not invalidated.
51+
pub(super) fn shallow_copy(&self) -> Self {
52+
Self {
53+
cursor: self.cursor,
54+
start: self.start,
55+
len: self.len,
56+
}
57+
}
58+
4659
/// Reset the cursor to the start.
4760
pub(super) fn reset_cursor(&mut self) {
4861
self.cursor = self.start;

src/bit_machine/mod.rs

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::{Cmr, FailEntropy, Value};
2222
use frame::Frame;
2323

2424
pub use self::limits::LimitError;
25-
pub use self::tracker::{ExecTracker, NoTracker, PruneTracker, SetTracker};
25+
pub use self::tracker::{ExecTracker, NoTracker, NodeOutput, PruneTracker, SetTracker};
2626

2727
/// An iterator over the contents of a read or write frame which yields bits.
2828
pub type FrameIter<'a> = crate::BitIter<core::iter::Copied<core::slice::Iter<'a, u8>>>;
@@ -271,6 +271,10 @@ impl BitMachine {
271271
}
272272

273273
'main_loop: loop {
274+
// Make a copy of the input frame to give to the tracker.
275+
let input_frame = self.read.last().map(Frame::shallow_copy);
276+
let mut jet_result = Ok(());
277+
274278
match ip.inner() {
275279
node::Inner::Unit => {}
276280
node::Inner::Iden => {
@@ -336,32 +340,18 @@ impl BitMachine {
336340
let (sum_a_b, _c) = ip.arrow().source.as_product().unwrap();
337341
let (a, b) = sum_a_b.as_sum().unwrap();
338342

339-
if tracker.is_track_debug_enabled() {
340-
if let node::Inner::AssertL(_, cmr) = ip.inner() {
341-
let mut bits = in_frame.as_bit_iter(&self.data);
342-
// Skips 1 + max(a.bit_width, b.bit_width) - a.bit_width
343-
bits.nth(a.pad_left(b))
344-
.expect("AssertL: unexpected end of frame");
345-
let value = Value::from_padded_bits(&mut bits, _c)
346-
.expect("AssertL: decode `C` value");
347-
tracker.track_dbg_call(cmr, value);
348-
}
349-
}
350-
351343
match (ip.inner(), choice_bit) {
352344
(node::Inner::Case(_, right), true)
353345
| (node::Inner::AssertR(_, right), true) => {
354346
self.fwd(1 + a.pad_right(b));
355347
call_stack.push(CallStack::Back(1 + a.pad_right(b)));
356348
call_stack.push(CallStack::Goto(right));
357-
tracker.track_right(ip.ihr());
358349
}
359350
(node::Inner::Case(left, _), false)
360351
| (node::Inner::AssertL(left, _), false) => {
361352
self.fwd(1 + a.pad_left(b));
362353
call_stack.push(CallStack::Back(1 + a.pad_left(b)));
363354
call_stack.push(CallStack::Goto(left));
364-
tracker.track_left(ip.ihr());
365355
}
366356
(node::Inner::AssertL(_, r_cmr), true) => {
367357
return Err(ExecutionError::ReachedPrunedBranch(*r_cmr))
@@ -373,13 +363,43 @@ impl BitMachine {
373363
}
374364
}
375365
node::Inner::Witness(value) => self.write_value(value),
376-
node::Inner::Jet(jet) => self.exec_jet(*jet, env, tracker)?,
366+
node::Inner::Jet(jet) => {
367+
jet_result = self.exec_jet(*jet, env);
368+
}
377369
node::Inner::Word(value) => self.write_value(value.as_value()),
378370
node::Inner::Fail(entropy) => {
379371
return Err(ExecutionError::ReachedFailNode(*entropy))
380372
}
381373
}
382374

375+
// Notify the tracker.
376+
{
377+
// Notice that, because the read frame stack is only ever
378+
// shortened by `drop_read_frame`, and that method was not
379+
// called above, this frame is still valid and correctly
380+
// describes the Bit Machine "input" to the current node,
381+
// no matter the node.
382+
let read_iter = input_frame
383+
.map(|frame| frame.as_bit_iter(&self.data))
384+
.unwrap_or(crate::BitIter::from([].iter().copied()));
385+
// See the docs on `tracker::NodeOutput` for more information about
386+
// this match.
387+
let output = match (ip.inner(), &jet_result) {
388+
(node::Inner::Unit | node::Inner::Iden | node::Inner::Witness(_), _)
389+
| (node::Inner::Jet(_), Ok(_)) => NodeOutput::Success(
390+
self.write
391+
.last()
392+
.map(|r| r.as_bit_iter(&self.data))
393+
.unwrap_or(crate::BitIter::from([].iter().copied())),
394+
),
395+
(node::Inner::Jet(_), Err(_)) => NodeOutput::JetFailed,
396+
_ => NodeOutput::NonTerminal,
397+
};
398+
tracker.visit_node(ip, read_iter, output);
399+
}
400+
// Fail if the jet failed.
401+
jet_result?;
402+
383403
ip = loop {
384404
match call_stack.pop() {
385405
Some(CallStack::Goto(next)) => break next,
@@ -410,12 +430,7 @@ impl BitMachine {
410430
}
411431
}
412432

413-
fn exec_jet<J: Jet, T: ExecTracker<J>>(
414-
&mut self,
415-
jet: J,
416-
env: &J::Environment,
417-
tracker: &mut T,
418-
) -> Result<(), JetFailed> {
433+
fn exec_jet<J: Jet>(&mut self, jet: J, env: &J::Environment) -> Result<(), JetFailed> {
419434
use crate::ffi::c_jets::frame_ffi::{c_readBit, c_writeBit, CFrameItem};
420435
use crate::ffi::c_jets::uword_width;
421436
use crate::ffi::ffi::UWORD;
@@ -501,15 +516,13 @@ impl BitMachine {
501516
let output_width = jet.target_ty().to_bit_width();
502517
// Input buffer is implicitly referenced by input read frame!
503518
// Same goes for output buffer
504-
let (input_read_frame, input_buffer) = unsafe { get_input_frame(self, input_width) };
519+
let (input_read_frame, _input_buffer) = unsafe { get_input_frame(self, input_width) };
505520
let (mut output_write_frame, output_buffer) = unsafe { get_output_frame(output_width) };
506521

507522
let jet_fn = jet.c_jet_ptr();
508523
let c_env = J::c_jet_env(env);
509524
let success = jet_fn(&mut output_write_frame, input_read_frame, c_env);
510525

511-
tracker.track_jet_call(&jet, &input_buffer, &output_buffer, success);
512-
513526
if !success {
514527
Err(JetFailed)
515528
} else {

src/bit_machine/tracker.rs

Lines changed: 78 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -8,40 +8,60 @@
88
//!
99
//! It is a private module but all types and traits are re-exported above.
1010
11-
use simplicity_sys::ffi::UWORD;
1211
use std::collections::HashSet;
1312

1413
use crate::jet::Jet;
15-
use crate::{Cmr, Ihr, Value};
14+
use crate::node::Inner;
15+
use crate::{Ihr, RedeemNode, Value};
1616

17-
/// A type that keeps track of Bit Machine execution.
17+
/// Write frame of a terminal (childless) Simplicity program node.
1818
///
19-
/// The trait is implemented for [`SetTracker`], that tracks which case branches were executed,
20-
/// and it is implemented for [`NoTracker`], which is a dummy tracker that is
21-
/// optimized out by the compiler.
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.
2223
///
23-
/// The trait enables us to turn tracking on or off depending on a generic parameter.
24-
pub trait ExecTracker<J: Jet> {
25-
/// Track the execution of the left branch of the case node with the given `ihr`.
26-
fn track_left(&mut self, ihr: Ihr);
27-
28-
/// Track the execution of the right branch of the case node with the given `ihr`.
29-
fn track_right(&mut self, ihr: Ihr);
30-
31-
/// Track the execution of a `jet` call with the given `input_buffer`, `output_buffer`, and call result `success`.
32-
fn track_jet_call(
33-
&mut self,
34-
jet: &J,
35-
input_buffer: &[UWORD],
36-
output_buffer: &[UWORD],
37-
success: bool,
38-
);
39-
40-
/// Track the potential execution of a `dbg!` call with the given `cmr` and `value`.
41-
fn track_dbg_call(&mut self, cmr: &Cmr, value: Value);
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)]
28+
pub 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+
}
4237

43-
/// Check if tracking debug calls is enabled.
44-
fn is_track_debug_enabled(&self) -> bool;
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.
45+
pub 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+
}
4565
}
4666

4767
pub trait PruneTracker<J: Jet>: ExecTracker<J> {
@@ -60,20 +80,21 @@ pub struct SetTracker {
6080
}
6181

6282
impl<J: Jet> ExecTracker<J> for SetTracker {
63-
fn track_left(&mut self, ihr: Ihr) {
64-
self.left.insert(ihr);
65-
}
66-
67-
fn track_right(&mut self, ihr: Ihr) {
68-
self.right.insert(ihr);
69-
}
70-
71-
fn track_jet_call(&mut self, _: &J, _: &[UWORD], _: &[UWORD], _: bool) {}
72-
73-
fn track_dbg_call(&mut self, _: &Cmr, _: Value) {}
74-
75-
fn is_track_debug_enabled(&self) -> bool {
76-
false
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+
}
7798
}
7899
}
79100

@@ -92,16 +113,21 @@ impl<J: Jet> PruneTracker<J> for SetTracker {
92113
pub struct NoTracker;
93114

94115
impl<J: Jet> ExecTracker<J> for NoTracker {
95-
fn track_left(&mut self, _: Ihr) {}
96-
97-
fn track_right(&mut self, _: Ihr) {}
98-
99-
fn track_jet_call(&mut self, _: &J, _: &[UWORD], _: &[UWORD], _: bool) {}
100-
101-
fn track_dbg_call(&mut self, _: &Cmr, _: Value) {}
102-
103-
fn is_track_debug_enabled(&self) -> bool {
104-
// Set flag to test frame decoding in unit tests
105-
cfg!(test)
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+
}
106132
}
107133
}

0 commit comments

Comments
 (0)