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