Skip to main content

simplicity/bit_machine/
mod.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! Simplicity Execution
4//!
5//! Implementation of the Bit Machine, without TCO, as TCO precludes some
6//! frame management optimizations which can be used to great benefit.
7//!
8
9mod frame;
10mod limits;
11mod tracker;
12
13use std::error;
14use std::fmt;
15use std::sync::Arc;
16
17use crate::analysis;
18use crate::jet::JetEnvironment;
19use crate::jet::{Jet, JetFailed};
20use crate::node::{self, RedeemNode};
21use crate::types::Final;
22use crate::{Cmr, FailEntropy, Value};
23use frame::Frame;
24
25pub use self::limits::LimitError;
26pub use self::tracker::{
27    ExecTracker, NoTracker, NodeOutput, PruneTracker, SetTracker, StderrTracker,
28};
29
30/// An iterator over the contents of a read or write frame which yields bits.
31pub type FrameIter<'a> = crate::BitIter<core::iter::Copied<core::slice::Iter<'a, u8>>>;
32
33/// An execution context for a Simplicity program
34pub struct BitMachine {
35    /// Space for bytes that read and write frames point to.
36    /// (De)allocation happens LIFO from left to right
37    data: Vec<u8>,
38    /// Top of data stack; index of first unused bit
39    next_frame_start: usize,
40    /// Read frame stack
41    read: Vec<Frame>,
42    /// Write frame stack
43    write: Vec<Frame>,
44    /// Acceptable source type
45    source_ty: Arc<Final>,
46}
47
48impl BitMachine {
49    /// Construct a Bit Machine with enough space to execute the given program.
50    pub fn for_program(program: &RedeemNode) -> Result<Self, LimitError> {
51        LimitError::check_program(program)?;
52        let io_width = program.arrow().source.bit_width() + program.arrow().target.bit_width();
53
54        Ok(Self {
55            data: vec![0; (io_width + program.bounds().extra_cells).div_ceil(8)],
56            next_frame_start: 0,
57            read: Vec::with_capacity(program.bounds().extra_frames + analysis::IO_EXTRA_FRAMES),
58            write: Vec::with_capacity(program.bounds().extra_frames + analysis::IO_EXTRA_FRAMES),
59            source_ty: program.arrow().source.clone(),
60        })
61    }
62
63    #[cfg(test)]
64    pub fn test_exec<JE: JetEnvironment>(
65        program: Arc<crate::node::ConstructNode>,
66        env: &JE,
67    ) -> Result<Value, ExecutionError> {
68        use crate::node::SimpleFinalizer;
69
70        let prog = program
71            .finalize_types_non_program()
72            .expect("finalizing types")
73            .finalize(&mut SimpleFinalizer::new(None.into_iter()))
74            .expect("finalizing");
75        let mut mac = BitMachine::for_program(&prog).expect("program has reasonable bounds");
76        mac.exec(&prog, env)
77    }
78
79    /// Push a new frame of given size onto the write frame stack
80    fn new_write_frame(&mut self, len: usize) {
81        debug_assert!(
82            self.next_frame_start + len <= self.data.len() * 8,
83            "Data out of bounds: number of cells"
84        );
85        debug_assert!(
86            self.write.len() + self.read.len() < self.read.capacity(),
87            "Stacks out of bounds: number of frames"
88        );
89
90        self.write.push(Frame::new(self.next_frame_start, len));
91        self.next_frame_start += len;
92    }
93
94    /// Move the active write frame to the read frame stack
95    fn move_write_frame_to_read(&mut self) {
96        let mut _active_write_frame = self.write.pop().unwrap();
97        _active_write_frame.reset_cursor();
98        self.read.push(_active_write_frame);
99    }
100
101    /// Drop the active read frame
102    fn drop_read_frame(&mut self) {
103        let active_read_frame = self.read.pop().unwrap();
104        self.next_frame_start -= active_read_frame.bit_width();
105        assert_eq!(self.next_frame_start, active_read_frame.start());
106    }
107
108    /// Write a single bit to the active write frame
109    fn write_bit(&mut self, bit: bool) {
110        self.write
111            .last_mut()
112            .expect("Empty write frame stack")
113            .write_bit(bit, &mut self.data);
114    }
115
116    /// Move the cursor of the active write frame forward by
117    /// a specified number of bits
118    fn skip(&mut self, n: usize) {
119        // short circuit n = 0
120        if n == 0 {
121            return;
122        }
123        let idx = self.write.len() - 1;
124        self.write[idx].move_cursor_forward(n);
125    }
126
127    /// Copy the given number of bits from the active read frame
128    /// to the active write frame
129    fn copy(&mut self, n: usize) {
130        // short circuit n = 0
131        if n == 0 {
132            return;
133        }
134        let widx = self.write.len() - 1;
135        let ridx = self.read.len() - 1;
136        self.write[widx].copy_from(&self.read[ridx], n, &mut self.data);
137    }
138
139    /// Move the cursor of the active read frame forward
140    /// by the given number of bits
141    fn fwd(&mut self, n: usize) {
142        // short circuit n = 0
143        if n == 0 {
144            return;
145        }
146        let idx = self.read.len() - 1;
147        self.read[idx].move_cursor_forward(n);
148    }
149
150    /// Move the cursor of the active read frame back
151    /// by the given number of bits
152    fn back(&mut self, n: usize) {
153        // short circuit n = 0
154        if n == 0 {
155            return;
156        }
157        let idx = self.read.len() - 1;
158        self.read[idx].move_cursor_backward(n);
159    }
160
161    /// Write a big-endian u8 value to the active write frame
162    fn write_u8(&mut self, value: u8) {
163        self.write
164            .last_mut()
165            .expect("Empty write frame stack")
166            .write_u8(value, &mut self.data);
167    }
168
169    /// Read a bit from the active read frame
170    fn read_bit(&mut self) -> bool {
171        self.read
172            .last_mut()
173            .expect("Empty read frame stack")
174            .read_bit(&self.data)
175    }
176
177    /// Write a bit string to the active write frame
178    fn write_bytes(&mut self, bytes: &[u8]) {
179        for bit in bytes {
180            self.write_u8(*bit);
181        }
182    }
183
184    /// Write a value to the current write frame
185    fn write_value(&mut self, val: &Value) {
186        for bit in val.iter_padded() {
187            self.write_bit(bit);
188        }
189    }
190
191    /// Return the bit width of the active read frame.
192    fn active_read_bit_width(&self) -> usize {
193        self.read.last().map(|frame| frame.bit_width()).unwrap_or(0)
194    }
195
196    /// Return the bit width of the active write frame.
197    fn active_write_bit_width(&self) -> usize {
198        self.write
199            .last()
200            .map(|frame| frame.bit_width())
201            .unwrap_or(0)
202    }
203
204    /// Add a read frame with some given value in it, as input to the
205    /// program
206    pub fn input(&mut self, input: &Value) -> Result<(), ExecutionError> {
207        if !input.is_of_type(&self.source_ty) {
208            return Err(ExecutionError::InputWrongType(self.source_ty.clone()));
209        }
210        // Unit value doesn't need extra frame
211        if !input.is_empty() {
212            self.new_write_frame(input.padded_len());
213            self.write_value(input);
214            self.move_write_frame_to_read();
215        }
216        Ok(())
217    }
218
219    /// Execute the given `program` on the Bit Machine, using the given environment.
220    ///
221    ///  ## Precondition
222    ///
223    /// The Bit Machine is constructed via [`Self::for_program()`] to ensure enough space.
224    pub fn exec<JE: JetEnvironment>(
225        &mut self,
226        program: &RedeemNode,
227        env: &JE,
228    ) -> Result<Value, ExecutionError> {
229        self.exec_with_tracker(program, env, &mut NoTracker)
230    }
231
232    /// Execute the given `program` on the Bit Machine, using the given environment and tracker.
233    ///
234    /// See [`crate::bit_machine::StderrTracker`] as an example which outputs various debug
235    /// data for each node, providing a track of the bit machine's operation.
236    ///
237    ///  ## Precondition
238    ///
239    /// The Bit Machine is constructed via [`Self::for_program()`] to ensure enough space.
240    pub fn exec_with_tracker<JE: JetEnvironment, T: ExecTracker>(
241        &mut self,
242        program: &RedeemNode,
243        env: &JE,
244        tracker: &mut T,
245    ) -> Result<Value, ExecutionError> {
246        enum CallStack<'a> {
247            Goto(&'a RedeemNode),
248            MoveWriteFrameToRead,
249            DropReadFrame,
250            CopyFwd(usize),
251            Back(usize),
252        }
253
254        // Not used, but useful for debugging, so keep it around
255        impl fmt::Debug for CallStack<'_> {
256            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
257                match self {
258                    CallStack::Goto(ins) => write!(f, "goto {}", ins.inner()),
259                    CallStack::MoveWriteFrameToRead => f.write_str("move frame"),
260                    CallStack::DropReadFrame => f.write_str("drop frame"),
261                    CallStack::CopyFwd(n) => write!(f, "copy/fwd {}", n),
262                    CallStack::Back(n) => write!(f, "back {}", n),
263                }
264            }
265        }
266
267        if self.read.is_empty() != self.source_ty.is_empty() {
268            return Err(ExecutionError::InputWrongType(self.source_ty.clone()));
269        }
270
271        let mut ip = program;
272        let mut call_stack: Vec<CallStack<'_>> = vec![];
273
274        let output_width = ip.arrow().target.bit_width();
275        if output_width > 0 {
276            self.new_write_frame(output_width);
277        }
278
279        'main_loop: loop {
280            // Capture read and write frames before the node action, to give to the tracker.
281            // The read frame cursor is where the node's input begins.
282            // The write frame cursor is where the node's output begins.
283            let input_frame = self.read.last().map(Frame::shallow_copy);
284            let output_frame = self.write.last().map(Frame::shallow_copy);
285            let mut jet_result = Ok(());
286
287            match ip.inner() {
288                node::Inner::Unit => {}
289                node::Inner::Iden => {
290                    let size_a = ip.arrow().source.bit_width();
291                    self.copy(size_a);
292                }
293                node::Inner::InjL(left) => {
294                    let (b, c) = ip.arrow().target.as_sum().unwrap();
295                    self.write_bit(false);
296                    self.skip(b.pad_left(c));
297                    call_stack.push(CallStack::Goto(left));
298                }
299                node::Inner::InjR(left) => {
300                    let (b, c) = ip.arrow().target.as_sum().unwrap();
301                    self.write_bit(true);
302                    self.skip(b.pad_right(c));
303                    call_stack.push(CallStack::Goto(left));
304                }
305                node::Inner::Pair(left, right) => {
306                    call_stack.push(CallStack::Goto(right));
307                    call_stack.push(CallStack::Goto(left));
308                }
309                node::Inner::Comp(left, right) => {
310                    let size_b = left.arrow().target.bit_width();
311
312                    self.new_write_frame(size_b);
313                    call_stack.push(CallStack::DropReadFrame);
314                    call_stack.push(CallStack::Goto(right));
315                    call_stack.push(CallStack::MoveWriteFrameToRead);
316                    call_stack.push(CallStack::Goto(left));
317                }
318                node::Inner::Disconnect(left, right) => {
319                    let size_prod_256_a = left.arrow().source.bit_width();
320                    let size_a = size_prod_256_a - 256;
321                    let size_prod_b_c = left.arrow().target.bit_width();
322                    let size_b = size_prod_b_c - right.arrow().source.bit_width();
323
324                    self.new_write_frame(size_prod_256_a);
325                    self.write_bytes(right.cmr().as_ref());
326                    self.copy(size_a);
327                    self.move_write_frame_to_read();
328                    self.new_write_frame(size_prod_b_c);
329
330                    // Remember that call stack pushes are executed in reverse order
331                    call_stack.push(CallStack::DropReadFrame);
332                    call_stack.push(CallStack::DropReadFrame);
333                    call_stack.push(CallStack::Goto(right));
334                    call_stack.push(CallStack::CopyFwd(size_b));
335                    call_stack.push(CallStack::MoveWriteFrameToRead);
336                    call_stack.push(CallStack::Goto(left));
337                }
338                node::Inner::Take(left) => call_stack.push(CallStack::Goto(left)),
339                node::Inner::Drop(left) => {
340                    let size_a = ip.arrow().source.as_product().unwrap().0.bit_width();
341                    self.fwd(size_a);
342                    call_stack.push(CallStack::Back(size_a));
343                    call_stack.push(CallStack::Goto(left));
344                }
345                node::Inner::Case(..) | node::Inner::AssertL(..) | node::Inner::AssertR(..) => {
346                    let in_frame = &self.read[self.read.len() - 1];
347                    let choice_bit: bool = in_frame.peek_bit(&self.data);
348
349                    let (sum_a_b, _c) = ip.arrow().source.as_product().unwrap();
350                    let (a, b) = sum_a_b.as_sum().unwrap();
351
352                    match (ip.inner(), choice_bit) {
353                        (node::Inner::Case(_, right), true)
354                        | (node::Inner::AssertR(_, right), true) => {
355                            self.fwd(1 + a.pad_right(b));
356                            call_stack.push(CallStack::Back(1 + a.pad_right(b)));
357                            call_stack.push(CallStack::Goto(right));
358                        }
359                        (node::Inner::Case(left, _), false)
360                        | (node::Inner::AssertL(left, _), false) => {
361                            self.fwd(1 + a.pad_left(b));
362                            call_stack.push(CallStack::Back(1 + a.pad_left(b)));
363                            call_stack.push(CallStack::Goto(left));
364                        }
365                        (node::Inner::AssertL(_, r_cmr), true) => {
366                            return Err(ExecutionError::ReachedPrunedBranch(*r_cmr))
367                        }
368                        (node::Inner::AssertR(l_cmr, _), false) => {
369                            return Err(ExecutionError::ReachedPrunedBranch(*l_cmr))
370                        }
371                        _ => unreachable!(),
372                    }
373                }
374                node::Inner::Witness(value) => self.write_value(value),
375                node::Inner::Jet(jet) => {
376                    let typed_jet = jet
377                        .as_ref()
378                        .as_any()
379                        .downcast_ref::<JE::Jet>()
380                        .ok_or(ExecutionError::JetTypeMismatch)?;
381                    jet_result = self.exec_jet(typed_jet, env);
382                }
383                node::Inner::Word(value) => self.write_value(value.as_value()),
384                node::Inner::Fail(entropy) => {
385                    return Err(ExecutionError::ReachedFailNode(*entropy))
386                }
387            }
388
389            // Notify the tracker.
390            {
391                // Notice that, because the read frame stack is only ever
392                // shortened by `drop_read_frame`, and that method was not
393                // called above, this frame is still valid and correctly
394                // describes the Bit Machine "input" to the current node,
395                // no matter the node.
396                let read_iter = input_frame
397                    .map(|frame| frame.as_bit_iter_from_cursor(&self.data))
398                    .unwrap_or(crate::BitIter::from([].iter().copied()));
399                // See the docs on `tracker::NodeOutput` for more information about
400                // this match.
401                let output = match (ip.inner(), &jet_result) {
402                    (node::Inner::Unit | node::Inner::Iden | node::Inner::Witness(_), _)
403                    | (node::Inner::Jet(_), Ok(_)) => NodeOutput::Success(
404                        output_frame
405                            .map(|frame| frame.as_bit_iter_from_cursor(&self.data))
406                            .unwrap_or(crate::BitIter::from([].iter().copied())),
407                    ),
408                    (node::Inner::Jet(_), Err(_)) => NodeOutput::JetFailed,
409                    _ => NodeOutput::NonTerminal,
410                };
411                tracker.visit_node(ip, read_iter, output);
412            }
413            // Fail if the jet failed.
414            jet_result?;
415
416            ip = loop {
417                match call_stack.pop() {
418                    Some(CallStack::Goto(next)) => break next,
419                    Some(CallStack::MoveWriteFrameToRead) => self.move_write_frame_to_read(),
420                    Some(CallStack::DropReadFrame) => self.drop_read_frame(),
421                    Some(CallStack::CopyFwd(n)) => {
422                        self.copy(n);
423                        self.fwd(n);
424                    }
425                    Some(CallStack::Back(n)) => self.back(n),
426                    None => break 'main_loop,
427                };
428            };
429        }
430
431        if output_width > 0 {
432            let out_frame = self.write.last_mut().unwrap();
433            out_frame.reset_cursor();
434            let value = Value::from_padded_bits(
435                &mut out_frame.as_bit_iter_from_cursor(&self.data),
436                &program.arrow().target,
437            )
438            .expect("Decode value of output frame");
439
440            Ok(value)
441        } else {
442            Ok(Value::unit())
443        }
444    }
445
446    fn exec_jet<JE: JetEnvironment>(&mut self, jet: &JE::Jet, env: &JE) -> Result<(), JetFailed> {
447        use crate::ffi::c_jets::frame_ffi::{c_readBit, c_writeBit, CFrameItem};
448        use crate::ffi::c_jets::uword_width;
449        use crate::ffi::ffi::UWORD;
450
451        /// Create new C read frame that contains `bit_width` many bits from active read frame.
452        ///
453        /// Return C read frame together with underlying buffer.
454        ///
455        /// ## Safety
456        ///
457        /// The returned frame must outlive its buffer or there is a dangling pointer.
458        ///
459        /// ## Panics
460        ///
461        /// Active read frame has fewer bits than `bit_width`.
462        unsafe fn get_input_frame(
463            mac: &mut BitMachine,
464            bit_width: usize,
465        ) -> (CFrameItem, Vec<UWORD>) {
466            assert!(bit_width <= mac.active_read_bit_width());
467            let uword_width = uword_width(bit_width);
468            let mut buffer = vec![0; uword_width];
469
470            // Copy bits from active read frame into input frame
471            let buffer_end = buffer.as_mut_ptr().add(uword_width);
472            let mut write_frame = CFrameItem::new_write(bit_width, buffer_end);
473            for _ in 0..bit_width {
474                let bit = mac.read_bit();
475                c_writeBit(&mut write_frame, bit);
476            }
477            mac.back(bit_width);
478
479            // Convert input frame into read frame
480            let buffer_ptr = buffer.as_mut_ptr();
481            let read_frame = CFrameItem::new_read(bit_width, buffer_ptr);
482
483            (read_frame, buffer)
484        }
485
486        /// Create C write frame that is as wide as `bit_width`.
487        ///
488        /// Return C write frame together with underlying buffer.
489        ///
490        /// ## Safety
491        ///
492        /// The returned frame must outlive its buffer or there is a dangling pointer.
493        unsafe fn get_output_frame(bit_width: usize) -> (CFrameItem, Vec<UWORD>) {
494            let uword_width = uword_width(bit_width);
495            let mut buffer = vec![0; uword_width];
496
497            // Return output frame as write frame
498            let buffer_end = buffer.as_mut_ptr().add(uword_width);
499            let write_frame = CFrameItem::new_write(bit_width, buffer_end);
500
501            (write_frame, buffer)
502        }
503
504        /// Write `bit_width` many bits from `buffer` into active write frame.
505        ///
506        /// ## Panics
507        ///
508        /// Active write frame has fewer bits than `bit_width`.
509        ///
510        /// Buffer has fewer than bits than `bit_width` (converted to UWORDs).
511        fn update_active_write_frame(mac: &mut BitMachine, bit_width: usize, buffer: &[UWORD]) {
512            assert!(bit_width <= mac.active_write_bit_width());
513            assert!(uword_width(bit_width) <= buffer.len());
514            let buffer_ptr = buffer.as_ptr();
515            let mut read_frame = unsafe { CFrameItem::new_read(bit_width, buffer_ptr) };
516
517            for _ in 0..bit_width {
518                let bit = unsafe { c_readBit(&mut read_frame) };
519                mac.write_bit(bit);
520            }
521        }
522
523        // Sanity Check: This should never really fail, but still good to do
524        if !simplicity_sys::c_jets::sanity_checks() {
525            return Err(JetFailed);
526        }
527
528        let input_width = jet.source_ty().to_bit_width();
529        let output_width = jet.target_ty().to_bit_width();
530        // Input buffer is implicitly referenced by input read frame!
531        // Same goes for output buffer
532        let (input_read_frame, _input_buffer) = unsafe { get_input_frame(self, input_width) };
533        let (mut output_write_frame, output_buffer) = unsafe { get_output_frame(output_width) };
534
535        let jet_fn = JE::c_jet_ptr(jet);
536        let c_env = env.c_jet_env();
537        let success = jet_fn(&mut output_write_frame, input_read_frame, c_env);
538
539        if !success {
540            Err(JetFailed)
541        } else {
542            update_active_write_frame(self, output_width, &output_buffer);
543            Ok(())
544        }
545    }
546}
547
548/// Errors related to simplicity Execution
549#[derive(Debug)]
550pub enum ExecutionError {
551    /// Provided input is of wrong type
552    InputWrongType(Arc<Final>),
553    /// Reached a fail node
554    ReachedFailNode(FailEntropy),
555    /// Reached a pruned branch
556    ReachedPrunedBranch(Cmr),
557    /// Exceeded some program limit
558    LimitExceeded(LimitError),
559    /// Jet failed during execution
560    JetFailed(JetFailed),
561    /// Jet type mismatch between program and execution environment
562    JetTypeMismatch,
563}
564
565impl fmt::Display for ExecutionError {
566    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
567        match self {
568            ExecutionError::InputWrongType(expected_ty) => {
569                write!(f, "Expected input of type: {expected_ty}")
570            }
571            ExecutionError::ReachedFailNode(entropy) => {
572                write!(f, "Execution reached a fail node: {}", entropy)
573            }
574            ExecutionError::ReachedPrunedBranch(hash) => {
575                write!(f, "Execution reached a pruned branch: {}", hash)
576            }
577            ExecutionError::LimitExceeded(e) => e.fmt(f),
578            ExecutionError::JetFailed(jet_failed) => jet_failed.fmt(f),
579            ExecutionError::JetTypeMismatch => {
580                f.write_str("Jet type mismatch between program and execution environment")
581            }
582        }
583    }
584}
585
586impl error::Error for ExecutionError {
587    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
588        match self {
589            Self::InputWrongType(..)
590            | Self::ReachedFailNode(..)
591            | Self::ReachedPrunedBranch(..)
592            | Self::JetTypeMismatch => None,
593            Self::LimitExceeded(ref e) => Some(e),
594            Self::JetFailed(ref e) => Some(e),
595        }
596    }
597}
598
599impl From<LimitError> for ExecutionError {
600    fn from(e: LimitError) -> Self {
601        ExecutionError::LimitExceeded(e)
602    }
603}
604
605impl From<JetFailed> for ExecutionError {
606    fn from(jet_failed: JetFailed) -> Self {
607        ExecutionError::JetFailed(jet_failed)
608    }
609}
610
611#[cfg(test)]
612mod tests {
613    use super::*;
614
615    #[cfg(feature = "elements")]
616    use crate::jet::elements::ElementsEnv;
617    use crate::jet::CoreEnv;
618    #[cfg(feature = "elements")]
619    use crate::{node::RedeemNode, BitIter};
620    #[cfg(feature = "elements")]
621    use hex::DisplayHex;
622
623    #[cfg(feature = "elements")]
624    fn run_program_elements(
625        prog_bytes: &[u8],
626        witness_bytes: &[u8],
627        cmr_str: &str,
628        amr_str: &str,
629        ihr_str: &str,
630    ) -> Result<Value, ExecutionError> {
631        let prog_hex = prog_bytes.as_hex();
632
633        let prog = BitIter::from(prog_bytes);
634        let witness = BitIter::from(witness_bytes);
635        let prog = match RedeemNode::decode::<_, _, crate::jet::Elements>(prog, witness) {
636            Ok(prog) => prog,
637            Err(e) => panic!("program {} failed: {}", prog_hex, e),
638        };
639
640        // Check Merkle roots; check AMR last because the AMR is a more complicated
641        // calculation and historically has been much more likely to be wrong.
642        assert_eq!(
643            prog.cmr().to_string(),
644            cmr_str,
645            "CMR mismatch (got {} expected {}) for program {}",
646            prog.cmr(),
647            cmr_str,
648            prog_hex,
649        );
650        assert_eq!(
651            prog.amr().to_string(),
652            amr_str,
653            "AMR mismatch (got {} expected {}) for program {}",
654            prog.amr(),
655            amr_str,
656            prog_hex,
657        );
658        assert_eq!(
659            prog.ihr().to_string(),
660            ihr_str,
661            "IHR mismatch (got {} expected {}) for program {}",
662            prog.ihr(),
663            ihr_str,
664            prog_hex,
665        );
666
667        // Try to run it on the bit machine and return the result
668        let env = ElementsEnv::dummy();
669        BitMachine::for_program(&prog)
670            .expect("program has reasonable bounds")
671            .exec(&prog, &env)
672    }
673
674    #[test]
675    #[cfg(feature = "human_encoding")]
676    fn set_tracker_cursor_regression() {
677        // When a `case` node executes inside another `case` or `drop`, the read frame cursor
678        // is no longer at the frame's start. Before this fix, `exec_with_tracker` passed an
679        // iterator starting at frame.start to the tracker, so `SetTracker` read the wrong bit
680        // and recorded the wrong branch, leading to an assertl/assertr mismatch after pruning.
681        //
682        // Program: comp (pair (injl (injr unit)) unit) (case (case unit unit) unit)
683        // Intermediate frame bits: [0=L-tag of outer, 1=R-tag of inner]
684        // Outer case: peek bit 0 = 0 (LEFT), fwd(1), cursor moves to bit 1.
685        // Inner case: should read bit 1 = 1 (RIGHT).
686        // Bug: tracker received iterator from bit 0, read 0 (LEFT) → pruned wrong branch.
687        use crate::human_encoding::Forest;
688        use crate::types;
689        use std::collections::HashMap;
690
691        types::Context::with_context(|ctx| {
692            let s = "main := comp (pair (injl (injr unit)) unit) (case (case unit unit) unit)";
693            let program = Forest::parse::<crate::jet::Core>(s)
694                .expect("parse")
695                .to_witness_node(&ctx, &HashMap::new())
696                .expect("main root")
697                .finalize_pruned(&CoreEnv::new())
698                .expect("finalize and prune");
699            let mut mac = BitMachine::for_program(&program).expect("for_program");
700            mac.exec(&program, &CoreEnv::new())
701                .expect("pruned execution should succeed");
702        });
703    }
704
705    #[test]
706    #[cfg(feature = "elements")]
707    fn crash_regression1() {
708        // cfe18fb44028870400
709        // This program caused an array OOB.
710        // # Witnesses
711        // wit1 = witness :: (((1 + (1 + 2^256)) * (1 + (1 + 2^256))) + 1) -> 1
712        //
713        // # Program code
714        // jt1 = jet_num_outputs    :: 1 -> 2^32                                          # cmr 447165a3...
715        // jt2 = jet_issuance_token :: 2^32 -> (1 + (1 + 2^256))                          # cmr 85e9591c...
716        // pr3 = pair jt2 jt2       :: 2^32 -> ((1 + (1 + 2^256)) * (1 + (1 + 2^256)))    # cmr 45d40848...
717        // cp4 = comp jt1 pr3       :: 1 -> ((1 + (1 + 2^256)) * (1 + (1 + 2^256)))       # cmr 7bb1824f...
718        // jl5 = injl cp4           :: 1 -> (((1 + (1 + 2^256)) * (1 + (1 + 2^256))) + 1) # cmr 277ee32c...
719        //
720        // main = comp jl5 wit1     :: 1 -> 1                                             # cmr 7050c4a6...
721
722        let res = run_program_elements(
723            &[0xcf, 0xe1, 0x8f, 0xb4, 0x40, 0x28, 0x87, 0x04, 0x00],
724            &[],
725            "0075f5368af7b453c2f7318493df12567bc6d733cf1ebb69a08ce93e1e529956",
726            "0ac9baaee94e416c6598a271dfc18d6014f751ac300cfe69eb758c20f26c624b",
727            "61f2cf59bfec55bb6e44cbae71c3ba225f1059f9a5679c0b42a7177daff52b5a",
728        );
729        assert_eq!(res.unwrap(), Value::unit());
730    }
731
732    #[test]
733    fn crash_regression2() {
734        use crate::node::{CoreConstructible as _, JetConstructible as _};
735
736        type Node<'brand> = Arc<crate::ConstructNode<'brand>>;
737
738        crate::types::Context::with_context(|ctx| {
739            let mut bomb = Node::jet(
740                &ctx,
741                &crate::jet::Core::Ch8, // arbitrary jet with nonzero output size
742            );
743            for _ in 0..100 {
744                bomb = Node::pair(&bomb, &bomb).unwrap();
745            }
746            let _ = bomb.finalize_pruned(&CoreEnv::new());
747        });
748    }
749}