Skip to content

Commit 6a8a265

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 25ff2d5 commit 6a8a265

File tree

3 files changed

+103
-75
lines changed

3 files changed

+103
-75
lines changed

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: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,9 @@ 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+
274277
match ip.inner() {
275278
node::Inner::Unit => {}
276279
node::Inner::Iden => {
@@ -336,32 +339,18 @@ impl BitMachine {
336339
let (sum_a_b, _c) = ip.arrow().source.as_product().unwrap();
337340
let (a, b) = sum_a_b.as_sum().unwrap();
338341

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-
351342
match (ip.inner(), choice_bit) {
352343
(node::Inner::Case(_, right), true)
353344
| (node::Inner::AssertR(_, right), true) => {
354345
self.fwd(1 + a.pad_right(b));
355346
call_stack.push(CallStack::Back(1 + a.pad_right(b)));
356347
call_stack.push(CallStack::Goto(right));
357-
tracker.track_right(ip.ihr());
358348
}
359349
(node::Inner::Case(left, _), false)
360350
| (node::Inner::AssertL(left, _), false) => {
361351
self.fwd(1 + a.pad_left(b));
362352
call_stack.push(CallStack::Back(1 + a.pad_left(b)));
363353
call_stack.push(CallStack::Goto(left));
364-
tracker.track_left(ip.ihr());
365354
}
366355
(node::Inner::AssertL(_, r_cmr), true) => {
367356
return Err(ExecutionError::ReachedPrunedBranch(*r_cmr))
@@ -373,13 +362,37 @@ impl BitMachine {
373362
}
374363
}
375364
node::Inner::Witness(value) => self.write_value(value),
376-
node::Inner::Jet(jet) => self.exec_jet(*jet, env, tracker)?,
365+
node::Inner::Jet(jet) => self.exec_jet(*jet, env)?,
377366
node::Inner::Word(value) => self.write_value(value.as_value()),
378367
node::Inner::Fail(entropy) => {
379368
return Err(ExecutionError::ReachedFailNode(*entropy))
380369
}
381370
}
382371

372+
// Notify the tracker.
373+
{
374+
// Notice that, because the read frame stack is only ever
375+
// shortened by `drop_read_frame`, and that method was not
376+
// called above, this frame is still valid and correctly
377+
// describes the Bit Machine "input" to the current node,
378+
// no matter the node.
379+
let read_iter = input_frame
380+
.map(|frame| frame.as_bit_iter(&self.data))
381+
.unwrap_or(crate::BitIter::from([].iter().copied()));
382+
// Ideally we could also provide the Bit Machine "output" here,
383+
// but for nonterminal nodes, we have not computed it and may
384+
// not even have allocated space for it. So instead we provide an
385+
// iterator which gives the output of terminal nodes (unit iden
386+
// witness jets) and document on [`ExecTracker::visit_node`] that
387+
// this iterator's contents are otherwise unspecified.
388+
let write_iter = self
389+
.write
390+
.last()
391+
.map(|r| r.as_bit_iter(&self.data))
392+
.unwrap_or(crate::BitIter::from([].iter().copied()));
393+
tracker.visit_node(ip, read_iter, write_iter);
394+
}
395+
383396
ip = loop {
384397
match call_stack.pop() {
385398
Some(CallStack::Goto(next)) => break next,
@@ -410,12 +423,7 @@ impl BitMachine {
410423
}
411424
}
412425

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> {
426+
fn exec_jet<J: Jet>(&mut self, jet: J, env: &J::Environment) -> Result<(), JetFailed> {
419427
use crate::ffi::c_jets::frame_ffi::{c_readBit, c_writeBit, CFrameItem};
420428
use crate::ffi::c_jets::uword_width;
421429
use crate::ffi::ffi::UWORD;
@@ -501,15 +509,13 @@ impl BitMachine {
501509
let output_width = jet.target_ty().to_bit_width();
502510
// Input buffer is implicitly referenced by input read frame!
503511
// Same goes for output buffer
504-
let (input_read_frame, input_buffer) = unsafe { get_input_frame(self, input_width) };
512+
let (input_read_frame, _input_buffer) = unsafe { get_input_frame(self, input_width) };
505513
let (mut output_write_frame, output_buffer) = unsafe { get_output_frame(output_width) };
506514

507515
let jet_fn = jet.c_jet_ptr();
508516
let c_env = J::c_jet_env(env);
509517
let success = jet_fn(&mut output_write_frame, input_read_frame, c_env);
510518

511-
tracker.track_jet_call(&jet, &input_buffer, &output_buffer, success);
512-
513519
if !success {
514520
Err(JetFailed)
515521
} else {

src/bit_machine/tracker.rs

Lines changed: 60 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -6,40 +6,47 @@
66
//! frame management optimizations which can be used to great benefit.
77
//!
88
9-
use simplicity_sys::ffi::UWORD;
109
use std::collections::HashSet;
1110

1211
use crate::jet::Jet;
13-
use crate::{Cmr, Ihr, Value};
12+
use crate::node::Inner;
13+
use crate::{Ihr, RedeemNode, Value};
1414

15-
/// A type that keeps track of Bit Machine execution.
15+
/// An object which can be used to introspect the execution of the Bit Machine.
1616
///
17-
/// The trait is implemented for [`SetTracker`], that tracks which case branches were executed,
18-
/// and it is implemented for [`NoTracker`], which is a dummy tracker that is
19-
/// optimized out by the compiler.
17+
/// As an example of what you can do with this
2018
///
21-
/// The trait enables us to turn tracking on or off depending on a generic parameter.
19+
/// If this tracker records accesses to the left and right children of `Case` nodes, you
20+
/// may want to also implement [`PruningTracker`] so that this data can be used by
21+
/// [`RedeemNode::prune_with_tracking`] to prune the program. The most straightforward
22+
/// way to do this is to embed a [`SetTracker`] in your tracker and forward all the trait
23+
/// methods to that.
2224
pub trait ExecTracker<J: Jet> {
23-
/// Track the execution of the left branch of the case node with the given `ihr`.
24-
fn track_left(&mut self, ihr: Ihr);
25-
26-
/// Track the execution of the right branch of the case node with the given `ihr`.
27-
fn track_right(&mut self, ihr: Ihr);
28-
29-
/// Track the execution of a `jet` call with the given `input_buffer`, `output_buffer`, and call result `success`.
30-
fn track_jet_call(
25+
/// Called immediately after a specific node of the program is executed, but before
26+
/// its children are executed.
27+
///
28+
/// More precisely, this iterates through the through the Simplicity program tree in
29+
/// *pre* ordering. That is, for the program `comp iden unit` the nodes will be visited
30+
/// in the order `comp`, `iden`, `unit`.
31+
///
32+
/// This method be used for logging, to track left or write accesses of the children of a
33+
/// `Case` node (to do this, call `input.peek_bit()`; false means left and true means right),
34+
/// to extract debug information (which may be embedded in the hidden CMR in `AssertL`
35+
/// and `AssertR` nodes, depending how the program was constructed), and so on.
36+
///
37+
/// The provided arguments are:
38+
/// * `node` is the node which was just visited.
39+
/// * `input` is an iterator over the read frame when the node's execution began
40+
/// * for terminal nodes (`witness`, `unit`, `iden` and jets), `output` is an iterator
41+
/// the write frame after the node has executed. For non-terminal nodes, the contents
42+
/// of this iterator are unspecified and should not be relied upon.
43+
fn visit_node<'d>(
3144
&mut self,
32-
jet: &J,
33-
input_buffer: &[UWORD],
34-
output_buffer: &[UWORD],
35-
success: bool,
36-
);
37-
38-
/// Track the potential execution of a `dbg!` call with the given `cmr` and `value`.
39-
fn track_dbg_call(&mut self, cmr: &Cmr, value: Value);
40-
41-
/// Check if tracking debug calls is enabled.
42-
fn is_track_debug_enabled(&self) -> bool;
45+
_node: &RedeemNode<J>,
46+
_input: super::FrameIter,
47+
_output: super::FrameIter,
48+
) {
49+
}
4350
}
4451

4552
pub trait PruneTracker<J: Jet>: ExecTracker<J> {
@@ -58,20 +65,21 @@ pub struct SetTracker {
5865
}
5966

6067
impl<J: Jet> ExecTracker<J> for SetTracker {
61-
fn track_left(&mut self, ihr: Ihr) {
62-
self.left.insert(ihr);
63-
}
64-
65-
fn track_right(&mut self, ihr: Ihr) {
66-
self.right.insert(ihr);
67-
}
68-
69-
fn track_jet_call(&mut self, _: &J, _: &[UWORD], _: &[UWORD], _: bool) {}
70-
71-
fn track_dbg_call(&mut self, _: &Cmr, _: Value) {}
72-
73-
fn is_track_debug_enabled(&self) -> bool {
74-
false
68+
fn visit_node<'d>(
69+
&mut self,
70+
node: &RedeemNode<J>,
71+
mut input: super::FrameIter,
72+
_output: super::FrameIter,
73+
) {
74+
match (node.inner(), input.next()) {
75+
(Inner::AssertL(..) | Inner::Case(..), Some(false)) => {
76+
self.left.insert(node.ihr());
77+
}
78+
(Inner::AssertR(..) | Inner::Case(..), Some(true)) => {
79+
self.right.insert(node.ihr());
80+
}
81+
_ => {}
82+
}
7583
}
7684
}
7785

@@ -90,16 +98,17 @@ impl<J: Jet> PruneTracker<J> for SetTracker {
9098
pub struct NoTracker;
9199

92100
impl<J: Jet> ExecTracker<J> for NoTracker {
93-
fn track_left(&mut self, _: Ihr) {}
94-
95-
fn track_right(&mut self, _: Ihr) {}
96-
97-
fn track_jet_call(&mut self, _: &J, _: &[UWORD], _: &[UWORD], _: bool) {}
98-
99-
fn track_dbg_call(&mut self, _: &Cmr, _: Value) {}
100-
101-
fn is_track_debug_enabled(&self) -> bool {
102-
// Set flag to test frame decoding in unit tests
103-
cfg!(test)
101+
fn visit_node<'d>(
102+
&mut self,
103+
node: &RedeemNode<J>,
104+
mut input: super::FrameIter,
105+
mut output: super::FrameIter,
106+
) {
107+
if cfg!(test) {
108+
// In unit tests, attempt to decode values from the frames, confirming that
109+
// these work.
110+
let _input_val = Value::from_padded_bits(&mut input, &node.arrow().source);
111+
let _output_val = Value::from_padded_bits(&mut output, &node.arrow().source);
112+
}
104113
}
105114
}

0 commit comments

Comments
 (0)