Skip to main content

tycho_vm/
state.rs

1use anyhow::Result;
2use bitflags::bitflags;
3use num_bigint::BigInt;
4#[cfg(feature = "tracing")]
5use tracing::instrument;
6use tycho_types::cell::*;
7use tycho_types::error::Error;
8use tycho_types::models::SignatureDomain;
9
10use crate::cont::{
11    AgainCont, ArgContExt, ControlData, ControlRegs, ExcQuitCont, OrdCont, QuitCont, RcCont,
12    RepeatCont, UntilCont, WhileCont,
13};
14use crate::dispatch::DispatchTable;
15use crate::error::{VmException, VmResult};
16use crate::gas::{GasConsumer, GasParams, LibraryProvider, NoLibraries, ParentGasConsumer};
17use crate::instr::{codepage, codepage0};
18use crate::saferc::SafeRc;
19use crate::smc_info::{SmcInfo, VmVersion};
20use crate::stack::{RcStackValue, Stack};
21use crate::util::OwnedCellSlice;
22
23/// Execution state builder.
24#[derive(Default)]
25pub struct VmStateBuilder<'a> {
26    pub code: Option<OwnedCellSlice>,
27    pub data: Option<Cell>,
28    pub stack: SafeRc<Stack>,
29    pub libraries: Option<&'a dyn LibraryProvider>,
30    pub c7: Option<SafeRc<Vec<RcStackValue>>>,
31    pub gas: GasParams,
32    pub init_selector: InitSelectorParams,
33    pub version: Option<VmVersion>,
34    pub modifiers: BehaviourModifiers,
35    pub debug: Option<&'a mut dyn std::fmt::Write>,
36}
37
38impl<'a> VmStateBuilder<'a> {
39    pub fn new() -> Self {
40        Self::default()
41    }
42
43    pub fn build(mut self) -> VmState<'a> {
44        static NO_LIBRARIES: NoLibraries = NoLibraries;
45
46        let quit0 = QUIT0.with(SafeRc::clone);
47        let quit1 = QUIT1.with(SafeRc::clone);
48        let cp = codepage0();
49
50        let (code, throw_on_code_access) = match self.code {
51            Some(code) => (code, false),
52            None => (Default::default(), true),
53        };
54
55        let c3 = match self.init_selector {
56            InitSelectorParams::None => QUIT11.with(SafeRc::clone).into_dyn_cont(),
57            InitSelectorParams::UseCode { push0 } => {
58                if push0 {
59                    vm_log_trace!("implicit PUSH 0 at start");
60                    SafeRc::make_mut(&mut self.stack)
61                        .items
62                        .push(Stack::make_zero());
63                }
64                SafeRc::from(OrdCont::simple(code.clone(), cp.id()))
65            }
66        };
67
68        let signature_domain_stack =
69            SafeRc::new(if let Some(global_id) = self.modifiers.signature_with_id {
70                vec![SignatureDomain::L2 { global_id }]
71            } else {
72                Vec::new()
73            });
74
75        VmState {
76            cr: ControlRegs {
77                c: [
78                    Some(quit0.clone().into_dyn_cont()),
79                    Some(quit1.clone().into_dyn_cont()),
80                    Some(EXC_QUIT.with(SafeRc::clone).into_dyn_cont()),
81                    Some(c3),
82                ],
83                d: [
84                    Some(self.data.unwrap_or_default()),
85                    Some(Cell::empty_cell()),
86                ],
87                c7: Some(self.c7.unwrap_or_default()),
88            },
89            code,
90            throw_on_code_access,
91            stack: self.stack,
92            signature_domains: signature_domain_stack,
93            committed_state: None,
94            steps: 0,
95            quit0,
96            quit1,
97            gas: GasConsumer::with_libraries(self.gas, self.libraries.unwrap_or(&NO_LIBRARIES)),
98            cp,
99            debug: self.debug,
100            modifiers: self.modifiers,
101            version: self.version.unwrap_or(VmState::DEFAULT_VERSION),
102            parent: None,
103        }
104    }
105
106    pub fn with_libraries<T: LibraryProvider>(mut self, libraries: &'a T) -> Self {
107        self.libraries = Some(libraries);
108        self
109    }
110
111    pub fn with_gas(mut self, gas: GasParams) -> Self {
112        self.gas = gas;
113        self
114    }
115
116    pub fn with_debug<T: std::fmt::Write>(mut self, stderr: &'a mut T) -> Self {
117        self.debug = Some(stderr);
118        self
119    }
120
121    pub fn with_code<T: IntoCode>(mut self, code: T) -> Self {
122        self.code = code.into_code().ok();
123        self
124    }
125
126    pub fn with_data(mut self, data: Cell) -> Self {
127        self.data = Some(data);
128        self
129    }
130
131    pub fn with_init_selector(mut self, push0: bool) -> Self {
132        self.init_selector = InitSelectorParams::UseCode { push0 };
133        self
134    }
135
136    pub fn with_stack<I: IntoIterator<Item = RcStackValue>>(mut self, values: I) -> Self {
137        self.stack = SafeRc::new(values.into_iter().collect());
138        self
139    }
140
141    pub fn with_raw_stack(mut self, stack: SafeRc<Stack>) -> Self {
142        self.stack = stack;
143        self
144    }
145
146    pub fn with_smc_info<T: SmcInfo>(mut self, info: T) -> Self {
147        if self.version.is_none() {
148            self.version = Some(info.version());
149        }
150        self.c7 = Some(info.build_c7());
151        self
152    }
153
154    pub fn with_modifiers(mut self, modifiers: BehaviourModifiers) -> Self {
155        self.modifiers = modifiers;
156        self
157    }
158
159    pub fn with_version(mut self, version: VmVersion) -> Self {
160        self.version = Some(version);
161        self
162    }
163}
164
165/// Anything that can be used as a VM code source.
166pub trait IntoCode {
167    fn into_code(self) -> Result<OwnedCellSlice, Error>;
168}
169
170impl<T: IntoCode> IntoCode for Option<T> {
171    fn into_code(self) -> Result<OwnedCellSlice, Error> {
172        match self {
173            Some(code) => code.into_code(),
174            None => Err(Error::CellUnderflow),
175        }
176    }
177}
178
179impl IntoCode for CellSliceParts {
180    #[inline]
181    fn into_code(self) -> Result<OwnedCellSlice, Error> {
182        Ok(OwnedCellSlice::from(self))
183    }
184}
185
186impl IntoCode for OwnedCellSlice {
187    #[inline]
188    fn into_code(self) -> Result<OwnedCellSlice, Error> {
189        Ok(self)
190    }
191}
192
193impl IntoCode for Cell {
194    fn into_code(mut self) -> Result<OwnedCellSlice, Error> {
195        let descriptor = self.descriptor();
196        if descriptor.is_exotic() {
197            if descriptor.is_library() {
198                // Special case for library cells as code root.
199                self = CellBuilder::build_from(self).unwrap();
200            } else {
201                // All other types are considered invalid.
202                return Err(Error::UnexpectedExoticCell);
203            }
204        }
205
206        Ok(OwnedCellSlice::new_allow_exotic(self))
207    }
208}
209
210/// Function selector (C3) initialization params.
211#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
212pub enum InitSelectorParams {
213    #[default]
214    None,
215    UseCode {
216        push0: bool,
217    },
218}
219
220/// Full execution state.
221pub struct VmState<'a> {
222    /// Current code slice.
223    pub code: OwnedCellSlice,
224    /// Whether VM state is completely invalid.
225    pub throw_on_code_access: bool,
226    /// Main VM stack.
227    pub stack: SafeRc<Stack>,
228    /// Signature domain stack.
229    pub signature_domains: SafeRc<Vec<SignatureDomain>>,
230    /// Control registers.
231    pub cr: ControlRegs,
232    /// Commited data and actions.
233    pub committed_state: Option<CommittedState>,
234    /// Number of execution steps.
235    pub steps: u64,
236    /// c0 continuation.
237    pub quit0: SafeRc<QuitCont>,
238    /// c1 continuation.
239    pub quit1: SafeRc<QuitCont>,
240    /// Gas usage.
241    pub gas: GasConsumer<'a>,
242    /// Current codepage.
243    pub cp: &'static DispatchTable,
244    /// Debug instructions output.
245    pub debug: Option<&'a mut dyn std::fmt::Write>,
246    /// VM behaviour modifiers.
247    pub modifiers: BehaviourModifiers,
248    /// Version.
249    pub version: VmVersion,
250    /// Parent VM state.
251    pub parent: Option<Box<ParentVmState<'a>>>,
252}
253
254/// Parent execution state.
255pub struct ParentVmState<'a> {
256    /// Parent code slice.
257    pub code: OwnedCellSlice,
258    /// Parent stack.
259    pub stack: SafeRc<Stack>,
260    /// Parent signature domain stack.
261    pub signature_domains: SafeRc<Vec<SignatureDomain>>,
262    /// Parent control registers.
263    pub cr: ControlRegs,
264    /// Parent committed state.
265    pub committed_state: Option<CommittedState>,
266    /// Parent VM steps.
267    pub steps: u64,
268    /// Parent c0 continuation.
269    pub quit0: SafeRc<QuitCont>,
270    /// Parent c1 continuation.
271    pub quit1: SafeRc<QuitCont>,
272    /// Gas to restore.
273    pub gas: ParentGasConsumer<'a>,
274    /// Parent codepage.
275    pub cp: &'static DispatchTable,
276
277    /// Push child c4 when restoring this state.
278    pub return_data: bool,
279    /// Push child c5 when restoring this state.
280    pub return_actions: bool,
281    /// Push consumed gas when restoring this state.
282    pub return_gas: bool,
283    /// Number of return values.
284    ///
285    /// `None` means that child stack will be merged with the parent.
286    pub return_values: Option<u32>,
287
288    /// Previous parent.
289    pub parent: Option<Box<ParentVmState<'a>>>,
290}
291
292impl<'a> VmState<'a> {
293    pub const DEFAULT_VERSION: VmVersion = VmVersion::LATEST_TON;
294
295    pub const MAX_DATA_DEPTH: u16 = 512;
296
297    thread_local! {
298        static EMPTY_STACK: SafeRc<Stack> = SafeRc::new(Default::default());
299    }
300
301    pub fn builder() -> VmStateBuilder<'a> {
302        VmStateBuilder::default()
303    }
304
305    #[cfg_attr(
306        feature = "tracing",
307        instrument(
308            level = "trace",
309            name = "vm_step",
310            fields(n = self.steps),
311            skip_all,
312        )
313    )]
314    pub fn step(&mut self) -> VmResult<i32> {
315        #[cfg(feature = "tracing")]
316        if self
317            .modifiers
318            .log_mask
319            .intersects(VmLogMask::DUMP_STACK.union(VmLogMask::DUMP_STACK_VERBOSE))
320        {
321            vm_log_stack!(
322                self.stack,
323                self.modifiers
324                    .log_mask
325                    .contains(VmLogMask::DUMP_STACK_VERBOSE)
326            );
327        }
328
329        self.steps += 1;
330        if !self.code.range().is_data_empty() {
331            #[cfg(feature = "tracing")]
332            if self.modifiers.log_mask.contains(VmLogMask::EXEC_LOCATION) {
333                let Size { bits, refs } = self.code.range().offset();
334                vm_log_exec_location!(self.code.cell(), bits, refs);
335            }
336
337            self.cp.dispatch(self)
338        } else if !self.code.range().is_refs_empty() {
339            vm_log_op!("implicit JMPREF");
340
341            let next_cell = self.code.apply().get_reference_cloned(0)?;
342
343            #[cfg(feature = "tracing")]
344            if self.modifiers.log_mask.contains(VmLogMask::EXEC_LOCATION) {
345                vm_log_exec_location!(next_cell, 0u16, 0u8);
346            }
347
348            self.gas.try_consume_implicit_jmpref_gas()?;
349            let code = self.gas.load_cell_as_slice(next_cell, LoadMode::Full)?;
350
351            let cont = SafeRc::from(OrdCont::simple(code, self.cp.id()));
352            self.jump(cont)
353        } else {
354            vm_log_op!("implicit RET");
355
356            self.gas.try_consume_implicit_ret_gas()?;
357            self.ret()
358        }
359    }
360
361    pub fn run(&mut self) -> i32 {
362        if self.throw_on_code_access {
363            // No negation for unhandled exceptions (to make their faking impossible).
364            return VmException::Fatal as u8 as i32;
365        }
366
367        let mut res = 0;
368        loop {
369            res = match self.restore_parent(!res) {
370                Ok(()) => self.run_inner(),
371                Err(OutOfGas) => {
372                    self.steps += 1;
373                    self.throw_out_of_gas()
374                }
375            };
376
377            if self.parent.is_none() {
378                #[cfg(feature = "tracing")]
379                if self.modifiers.log_mask.contains(VmLogMask::DUMP_C5)
380                    && let Some(committed) = &self.committed_state
381                {
382                    vm_log_c5!(committed.c5.as_ref());
383                }
384                break res;
385            }
386        }
387    }
388
389    fn run_inner(&mut self) -> i32 {
390        let mut res = 0;
391        while res == 0 {
392            let step_res = self.step();
393
394            #[cfg(feature = "tracing")]
395            if self.modifiers.log_mask.contains(VmLogMask::GAS_REMAINING) {
396                vm_log_gas_remaining!(self.gas.remaining());
397            }
398
399            #[cfg(feature = "tracing")]
400            if self.modifiers.log_mask.contains(VmLogMask::GAS_CONSUMED) {
401                vm_log_gas_consumed!(self.gas.consumed());
402            }
403
404            res = match step_res {
405                Ok(res) => res,
406                Err(e) if e.is_out_of_gas() => {
407                    self.steps += 1;
408                    self.throw_out_of_gas()
409                }
410                Err(e) => {
411                    let exception = e.as_exception();
412                    vm_log_trace!("handling exception {exception:?}: {e:?}");
413
414                    self.steps += 1;
415                    match self.throw_exception(exception as i32) {
416                        Ok(res) => res,
417                        Err(e) if e.is_out_of_gas() => {
418                            self.steps += 1;
419                            self.throw_out_of_gas()
420                        }
421                        Err(e) => {
422                            vm_log_trace!("double exception {exception:?}: {e:?}");
423                            return exception.as_exit_code();
424                        }
425                    }
426                }
427            };
428        }
429
430        // Try commit on ~(0) and ~(-1) exit codes
431        if res | 1 == -1 && !self.try_commit() {
432            vm_log_trace!("automatic commit failed");
433            self.stack = SafeRc::new(Stack {
434                items: vec![Stack::make_zero()],
435            });
436            return VmException::CellOverflow.as_exit_code();
437        }
438
439        res
440    }
441
442    pub fn try_commit(&mut self) -> bool {
443        if let (Some(c4), Some(c5)) = (&self.cr.d[0], &self.cr.d[1])
444            && c4.level() == 0
445            && c5.level() == 0
446            && c4.repr_depth() <= Self::MAX_DATA_DEPTH
447            && c5.repr_depth() <= Self::MAX_DATA_DEPTH
448        {
449            self.committed_state = Some(CommittedState {
450                c4: c4.clone(),
451                c5: c5.clone(),
452            });
453            return true;
454        }
455
456        false
457    }
458
459    pub fn force_commit(&mut self) -> Result<(), Error> {
460        if self.try_commit() {
461            Ok(())
462        } else {
463            Err(Error::CellOverflow)
464        }
465    }
466
467    pub fn take_stack(&mut self) -> SafeRc<Stack> {
468        std::mem::replace(&mut self.stack, Self::EMPTY_STACK.with(SafeRc::clone))
469    }
470
471    pub fn ref_to_cont(&mut self, code: Cell) -> VmResult<RcCont> {
472        let code = self.gas.load_cell_as_slice(code, LoadMode::Full)?;
473        Ok(SafeRc::from(OrdCont::simple(code, self.cp.id())))
474    }
475
476    pub fn c1_envelope_if(&mut self, cond: bool, cont: RcCont, save: bool) -> RcCont {
477        if cond {
478            self.c1_envelope(cont, save)
479        } else {
480            cont
481        }
482    }
483
484    pub fn c1_envelope(&mut self, mut cont: RcCont, save: bool) -> RcCont {
485        if save {
486            if cont.get_control_data().is_none() {
487                let mut c = ArgContExt {
488                    data: Default::default(),
489                    ext: cont,
490                };
491                c.data.save.define_c0(&self.cr.c[0]);
492                c.data.save.define_c1(&self.cr.c[1]);
493
494                cont = SafeRc::from(c);
495            } else {
496                let cont = SafeRc::make_mut(&mut cont);
497                if let Some(data) = cont.get_control_data_mut() {
498                    data.save.define_c0(&self.cr.c[0]);
499                    data.save.define_c1(&self.cr.c[1]);
500                }
501            }
502        }
503        self.cr.c[1] = Some(cont.clone());
504        cont
505    }
506
507    pub fn c1_save_set(&mut self) {
508        let [c0, c1, ..] = &mut self.cr.c;
509
510        if let Some(c0) = c0 {
511            if c0.get_control_data().is_none() {
512                let mut c = ArgContExt {
513                    data: Default::default(),
514                    ext: c0.clone(),
515                };
516                c.data.save.define_c1(c1);
517                *c0 = SafeRc::from(c);
518            } else {
519                let c0 = SafeRc::make_mut(c0);
520                if let Some(data) = c0.get_control_data_mut() {
521                    data.save.define_c1(c1);
522                }
523            }
524        }
525
526        c1.clone_from(c0);
527    }
528
529    pub fn extract_cc(
530        &mut self,
531        mode: SaveCr,
532        stack_copy: Option<u16>,
533        nargs: Option<u16>,
534    ) -> VmResult<RcCont> {
535        let new_stack = match stack_copy {
536            Some(0) => None,
537            Some(n) if (n as usize) != self.stack.depth() => {
538                let stack = ok!(SafeRc::make_mut(&mut self.stack)
539                    .split_top(n as _)
540                    .map(Some));
541                self.gas.try_consume_stack_gas(stack.as_ref())?;
542                stack
543            }
544            _ => Some(self.take_stack()),
545        };
546
547        let mut res = OrdCont {
548            code: std::mem::take(&mut self.code),
549            data: ControlData {
550                nargs,
551                stack: Some(self.take_stack()),
552                save: Default::default(),
553                cp: Some(self.cp.id()),
554            },
555        };
556        if let Some(new_stack) = new_stack {
557            self.stack = new_stack;
558        }
559
560        if mode.contains(SaveCr::C0) {
561            res.data.save.c[0] = self.cr.c[0].replace(self.quit0.clone().into_dyn_cont());
562        }
563        if mode.contains(SaveCr::C1) {
564            res.data.save.c[1] = self.cr.c[1].replace(self.quit1.clone().into_dyn_cont());
565        }
566        if mode.contains(SaveCr::C2) {
567            res.data.save.c[2] = self.cr.c[2].take();
568        }
569
570        Ok(SafeRc::from(res))
571    }
572
573    pub fn throw_exception(&mut self, n: i32) -> VmResult<i32> {
574        self.stack = SafeRc::new(Stack {
575            items: vec![Stack::make_zero(), SafeRc::new_dyn_value(BigInt::from(n))],
576        });
577        self.code = Default::default();
578        self.gas.try_consume_exception_gas()?;
579        let Some(c2) = self.cr.c[2].clone() else {
580            vm_bail!(InvalidOpcode);
581        };
582        self.jump(c2)
583    }
584
585    pub fn throw_exception_with_arg(&mut self, n: i32, arg: RcStackValue) -> VmResult<i32> {
586        self.stack = SafeRc::new(Stack {
587            items: vec![arg, SafeRc::new_dyn_value(BigInt::from(n))],
588        });
589        self.code = Default::default();
590        self.gas.try_consume_exception_gas()?;
591        let Some(c2) = self.cr.c[2].clone() else {
592            vm_bail!(InvalidOpcode);
593        };
594        self.jump(c2)
595    }
596
597    pub fn throw_out_of_gas(&mut self) -> i32 {
598        let consumed = self.gas.consumed();
599        vm_log_trace!(
600            "out of gas: consumed={consumed}, limit={}",
601            self.gas.limit(),
602        );
603        self.stack = SafeRc::new(Stack {
604            items: vec![SafeRc::new_dyn_value(BigInt::from(consumed))],
605        });
606
607        // No negation for unhandled exceptions (to make their faking impossible).
608        VmException::OutOfGas as u8 as i32
609    }
610
611    pub fn call(&mut self, cont: RcCont) -> VmResult<i32> {
612        if let Some(control_data) = cont.get_control_data() {
613            if control_data.save.c[0].is_some() {
614                // If cont has c0 then call reduces to a jump
615                return self.jump(cont);
616            }
617            if control_data.stack.is_some() || control_data.nargs.is_some() {
618                // If cont has non-empty stack or expects a fixed number of
619                // arguments, call is not simple
620                return self.call_ext(cont, None, None);
621            }
622        }
623
624        // Create return continuation
625        let mut ret = OrdCont::simple(std::mem::take(&mut self.code), self.cp.id());
626        ret.data.save.c[0] = self.cr.c[0].take();
627        self.cr.c[0] = Some(SafeRc::from(ret));
628
629        // NOTE: cont.data.save.c[0] must not be set
630        self.do_jump_to(cont)
631    }
632
633    pub fn call_ext(
634        &mut self,
635        mut cont: RcCont,
636        pass_args: Option<u16>,
637        ret_args: Option<u16>,
638    ) -> VmResult<i32> {
639        let (new_stack, c0) = if let Some(control_data) = cont.get_control_data() {
640            if control_data.save.c[0].is_some() {
641                // If cont has c0 then call reduces to a jump
642                return self.jump_ext(cont, pass_args);
643            }
644
645            let current_depth = self.stack.depth();
646            vm_ensure!(
647                pass_args.unwrap_or_default() as usize <= current_depth
648                    && control_data.nargs.unwrap_or_default() as usize <= current_depth,
649                StackUnderflow(std::cmp::max(
650                    pass_args.unwrap_or_default(),
651                    control_data.nargs.unwrap_or_default()
652                ) as _)
653            );
654
655            if let Some(pass_args) = pass_args {
656                vm_ensure!(
657                    control_data.nargs.unwrap_or_default() <= pass_args,
658                    StackUnderflow(pass_args as _)
659                );
660            }
661
662            let old_c0 = self.cr.c[0].take();
663            self.cr.preclear(&control_data.save);
664
665            let (copy, skip) = match (pass_args, control_data.nargs) {
666                (Some(pass_args), Some(copy)) => (Some(copy as usize), (pass_args - copy) as usize),
667                (Some(pass_args), None) => (Some(pass_args as usize), 0),
668                _ => (None, 0),
669            };
670
671            let new_stack = match SafeRc::get_mut(&mut cont) {
672                Some(cont) => cont
673                    .get_control_data_mut()
674                    .and_then(|control_data| control_data.stack.take()),
675                None => cont
676                    .get_control_data()
677                    .and_then(|control_data| control_data.stack.clone()),
678            };
679
680            let new_stack = match new_stack {
681                Some(mut new_stack) if !new_stack.items.is_empty() => {
682                    let copy = copy.unwrap_or(current_depth);
683
684                    let current_stack = SafeRc::make_mut(&mut self.stack);
685                    ok!(SafeRc::make_mut(&mut new_stack).move_from_stack(current_stack, copy));
686                    ok!(current_stack.pop_many(skip));
687
688                    self.gas.try_consume_stack_gas(Some(&new_stack))?;
689
690                    new_stack
691                }
692                _ => {
693                    if let Some(copy) = copy {
694                        let new_stack =
695                            ok!(SafeRc::make_mut(&mut self.stack).split_top_ext(copy, skip));
696
697                        self.gas.try_consume_stack_gas(Some(&new_stack))?;
698
699                        new_stack
700                    } else {
701                        self.take_stack()
702                    }
703                }
704            };
705
706            (new_stack, old_c0)
707        } else {
708            // Simple case without continuation data
709            let new_stack = if let Some(pass_args) = pass_args {
710                let new_stack = ok!(SafeRc::make_mut(&mut self.stack).split_top(pass_args as _));
711                self.gas.try_consume_stack_gas(Some(&new_stack))?;
712                new_stack
713            } else {
714                self.take_stack()
715            };
716
717            (new_stack, self.cr.c[0].take())
718        };
719
720        // Create a new stack from the top `pass_args` items of the current stack
721        let mut ret = OrdCont {
722            code: std::mem::take(&mut self.code),
723            data: ControlData {
724                save: Default::default(),
725                nargs: ret_args,
726                stack: Some(std::mem::replace(&mut self.stack, new_stack)),
727                cp: Some(self.cp.id()),
728            },
729        };
730        ret.data.save.c[0] = c0;
731        self.cr.c[0] = Some(SafeRc::from(ret));
732
733        self.do_jump_to(cont)
734    }
735
736    pub fn jump(&mut self, cont: RcCont) -> VmResult<i32> {
737        if let Some(cont_data) = cont.get_control_data()
738            && (cont_data.stack.is_some() || cont_data.nargs.is_some())
739        {
740            // Cont has a non-empty stack or expects a fixed number of arguments
741            return self.jump_ext(cont, None);
742        }
743
744        // The simplest continuation case:
745        // - the continuation doesn't have its own stack
746        // - `nargs` is not specified so it expects the full current stack
747        //
748        // So, we don't need to change anything to call it
749        self.do_jump_to(cont)
750    }
751
752    pub fn jump_ext(&mut self, cont: RcCont, pass_args: Option<u16>) -> VmResult<i32> {
753        // Either all values or the top n values in the current stack are
754        // moved to the stack of the continuation, and only then is the
755        // remainder of the current stack discarded.
756        let cont = ok!(self.adjust_jump_cont(cont, pass_args));
757
758        // Proceed to the continuation
759        self.do_jump_to(cont)
760    }
761
762    fn adjust_jump_cont(&mut self, mut cont: RcCont, pass_args: Option<u16>) -> VmResult<RcCont> {
763        if let Some(control_data) = cont.get_control_data() {
764            // n = self.stack.depth()
765            // if has nargs:
766            //     # From docs:
767            //     n' = control_data.nargs - control_data.stack.depth()
768            //     # From c++ impl:
769            //     n' = control_data.nargs
770            //     assert n' <= n
771            // if pass_args is specified:
772            //     n" = pass_args
773            //     assert n" >= n'
774            //
775            // - n" (or n) of args are taken from the current stack
776            // - n' (or n) of args are passed to the continuation
777
778            let current_depth = self.stack.depth();
779            vm_ensure!(
780                pass_args.unwrap_or_default() as usize <= current_depth
781                    && control_data.nargs.unwrap_or_default() as usize <= current_depth,
782                StackUnderflow(std::cmp::max(
783                    pass_args.unwrap_or_default(),
784                    control_data.nargs.unwrap_or_default()
785                ) as usize)
786            );
787
788            if let Some(pass_args) = pass_args {
789                vm_ensure!(
790                    control_data.nargs.unwrap_or_default() <= pass_args,
791                    StackUnderflow(pass_args as usize)
792                );
793            }
794
795            // Prepare the current savelist to be overwritten by the continuation
796            self.preclear_cr(&control_data.save);
797
798            // Compute the next stack depth
799            let next_depth = control_data
800                .nargs
801                .or(pass_args)
802                .map(|n| n as usize)
803                .unwrap_or(current_depth);
804
805            // Try to reuse continuation stack to reduce copies
806            let cont_stack = match SafeRc::get_mut(&mut cont) {
807                None => cont
808                    .get_control_data()
809                    .and_then(|control_data| control_data.stack.clone()),
810                Some(cont) => cont
811                    .get_control_data_mut()
812                    .and_then(|control_data| control_data.stack.take()),
813            };
814
815            match cont_stack {
816                // If continuation has a non-empty stack, extend it from the current stack
817                Some(mut cont_stack) if !cont_stack.items.is_empty() => {
818                    // TODO?: don't copy `self.stack` here
819                    ok!(SafeRc::make_mut(&mut cont_stack)
820                        .move_from_stack(SafeRc::make_mut(&mut self.stack), next_depth));
821                    self.gas.try_consume_stack_gas(Some(&cont_stack))?;
822
823                    self.stack = cont_stack;
824                }
825                // Ensure that the current stack has an exact number of items
826                _ if next_depth < current_depth => {
827                    ok!(SafeRc::make_mut(&mut self.stack).drop_bottom(current_depth - next_depth));
828                    self.gas.try_consume_stack_depth_gas(next_depth as _)?;
829                }
830                // Leave the current stack untouched
831                _ => {}
832            }
833        } else if let Some(pass_args) = pass_args {
834            // Try to leave only `pass_args` number of arguments in the current stack
835            let Some(depth_diff) = self.stack.depth().checked_sub(pass_args as _) else {
836                vm_bail!(StackUnderflow(pass_args as _));
837            };
838
839            if depth_diff > 0 {
840                // Modify the current stack only when needed
841                ok!(SafeRc::make_mut(&mut self.stack).drop_bottom(depth_diff));
842                self.gas.try_consume_stack_depth_gas(pass_args as _)?;
843            }
844        }
845
846        Ok(cont)
847    }
848
849    fn do_jump_to(&mut self, mut cont: RcCont) -> VmResult<i32> {
850        let mut exit_code = 0;
851        let mut count = 0;
852        while let Some(next) = ok!(SafeRc::into_inner(cont).jump(self, &mut exit_code)) {
853            cont = next;
854            count += 1;
855
856            // TODO: Check version >= 9?
857            if count > GasConsumer::FREE_NESTED_CONT_JUMP {
858                self.gas.try_consume(1)?;
859            }
860
861            if let Some(cont_data) = cont.get_control_data()
862                && (cont_data.stack.is_some() || cont_data.nargs.is_some())
863            {
864                // Cont has a non-empty stack or expects a fixed number of arguments
865                cont = ok!(self.adjust_jump_cont(cont, None));
866            }
867        }
868
869        Ok(exit_code)
870    }
871
872    pub fn ret(&mut self) -> VmResult<i32> {
873        let cont = ok!(self.take_c0());
874        self.jump(cont)
875    }
876
877    pub fn ret_ext(&mut self, ret_args: Option<u16>) -> VmResult<i32> {
878        let cont = ok!(self.take_c0());
879        self.jump_ext(cont, ret_args)
880    }
881
882    pub fn ret_alt(&mut self) -> VmResult<i32> {
883        let cont = ok!(self.take_c1());
884        self.jump(cont)
885    }
886
887    pub fn ret_alt_ext(&mut self, ret_args: Option<u16>) -> VmResult<i32> {
888        let cont = ok!(self.take_c1());
889        self.jump_ext(cont, ret_args)
890    }
891
892    pub fn repeat(&mut self, body: RcCont, after: RcCont, n: u32) -> VmResult<i32> {
893        self.jump(if n == 0 {
894            drop(body);
895            after
896        } else {
897            SafeRc::from(RepeatCont {
898                count: n as _,
899                body,
900                after,
901            })
902        })
903    }
904
905    pub fn until(&mut self, body: RcCont, after: RcCont) -> VmResult<i32> {
906        if !body.has_c0() {
907            self.cr.c[0] = Some(SafeRc::from(UntilCont {
908                body: body.clone(),
909                after,
910            }))
911        }
912        self.jump(body)
913    }
914
915    pub fn loop_while(&mut self, cond: RcCont, body: RcCont, after: RcCont) -> VmResult<i32> {
916        if !cond.has_c0() {
917            self.cr.c[0] = Some(SafeRc::from(WhileCont {
918                check_cond: true,
919                cond: cond.clone(),
920                body,
921                after,
922            }));
923        }
924        self.jump(cond)
925    }
926
927    pub fn again(&mut self, body: RcCont) -> VmResult<i32> {
928        self.jump(SafeRc::from(AgainCont { body }))
929    }
930
931    pub fn adjust_cr(&mut self, save: &ControlRegs) {
932        self.cr.merge(save)
933    }
934
935    pub fn preclear_cr(&mut self, save: &ControlRegs) {
936        self.cr.preclear(save)
937    }
938
939    pub fn set_c0(&mut self, cont: RcCont) {
940        self.cr.c[0] = Some(cont);
941    }
942
943    pub fn set_code(&mut self, code: OwnedCellSlice, cp: u16) -> VmResult<()> {
944        self.code = code;
945        self.force_cp(cp)
946    }
947
948    pub fn force_cp(&mut self, cp: u16) -> VmResult<()> {
949        let Some(cp) = codepage(cp) else {
950            vm_bail!(InvalidOpcode);
951        };
952        self.cp = cp;
953        Ok(())
954    }
955
956    fn take_c0(&mut self) -> VmResult<RcCont> {
957        let Some(cont) = self.cr.c[0].replace(self.quit0.clone().into_dyn_cont()) else {
958            vm_bail!(InvalidOpcode);
959        };
960        Ok(cont)
961    }
962
963    fn take_c1(&mut self) -> VmResult<RcCont> {
964        let Some(cont) = self.cr.c[1].replace(self.quit1.clone().into_dyn_cont()) else {
965            vm_bail!(InvalidOpcode);
966        };
967        Ok(cont)
968    }
969
970    fn restore_parent(&mut self, mut res: i32) -> Result<(), OutOfGas> {
971        let Some(parent) = self.parent.take() else {
972            return Ok(());
973        };
974
975        let steps = self.steps;
976
977        // Restore all values first.
978        self.code = parent.code;
979        let child_stack = std::mem::replace(&mut self.stack, parent.stack);
980        self.signature_domains = parent.signature_domains;
981        self.cr = parent.cr;
982        let child_committed_state =
983            std::mem::replace(&mut self.committed_state, parent.committed_state);
984        self.steps += parent.steps;
985        self.quit0 = parent.quit0;
986        self.quit1 = parent.quit1;
987        let child_gas = self.gas.restore(parent.gas);
988        self.cp = parent.cp;
989        self.parent = parent.parent;
990
991        vm_log_trace!(
992            "child vm finished: res={res}, steps={steps}, gas={}",
993            child_gas.gas_consumed
994        );
995
996        // === Apply child VM results to the restored state ===
997
998        // NOTE: Stack overflow errors are ignored here because it is impossible
999        //       to handle them properly here.
1000        // TODO: Somehow handle stack overflow errors. What should we do in that case?
1001
1002        // Consume isolated gas by the parent gas consumer.
1003        let amount = std::cmp::min(
1004            child_gas.gas_consumed,
1005            child_gas.gas_limit.saturating_add(1),
1006        );
1007        if self.gas.try_consume(amount).is_err() {
1008            return Err(OutOfGas);
1009        }
1010
1011        let stack = SafeRc::make_mut(&mut self.stack);
1012        let stack = &mut stack.items;
1013
1014        let returned_values = parent.return_values;
1015        let returned_values = if res == 0 || res == 1 {
1016            match returned_values {
1017                Some(n) if child_stack.depth() < n as usize => {
1018                    res = VmException::StackUnderflow.as_exit_code();
1019                    stack.push(Stack::make_zero());
1020                    0
1021                }
1022                Some(n) => n as usize,
1023                None => child_stack.depth(),
1024            }
1025        } else {
1026            std::cmp::min(child_stack.depth(), 1)
1027        };
1028
1029        let gas = &mut self.gas;
1030        if gas.try_consume_stack_depth_gas(returned_values).is_err() {
1031            return Err(OutOfGas);
1032        }
1033
1034        stack.extend_from_slice(&child_stack.items[child_stack.depth() - returned_values..]);
1035
1036        stack.push(SafeRc::new_dyn_value(BigInt::from(res)));
1037
1038        let (committed_c4, committed_c5) = match child_committed_state {
1039            Some(CommittedState { c4, c5 }) => (Some(c4), Some(c5)),
1040            None => (None, None),
1041        };
1042        if parent.return_data {
1043            stack.push(match committed_c4 {
1044                None => Stack::make_null(),
1045                Some(cell) => SafeRc::new_dyn_value(cell),
1046            })
1047        }
1048        if parent.return_actions {
1049            stack.push(match committed_c5 {
1050                None => Stack::make_null(),
1051                Some(cell) => SafeRc::new_dyn_value(cell),
1052            })
1053        }
1054        if parent.return_gas {
1055            stack.push(SafeRc::new_dyn_value(BigInt::from(child_gas.gas_consumed)));
1056        }
1057
1058        Ok(())
1059    }
1060}
1061
1062struct OutOfGas;
1063
1064/// Falgs to control VM behaviour.
1065#[derive(Default, Debug, Clone, Copy)]
1066pub struct BehaviourModifiers {
1067    pub stop_on_accept: bool,
1068    pub chksig_always_succeed: bool,
1069    pub enable_signature_domains: bool,
1070    pub signature_with_id: Option<i32>,
1071    #[cfg(feature = "tracing")]
1072    pub log_mask: VmLogMask,
1073}
1074
1075#[cfg(feature = "tracing")]
1076bitflags! {
1077    /// VM parts to log.
1078    #[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
1079    pub struct VmLogMask: u8 {
1080        const MESSAGE = 1 << 0;
1081        const DUMP_STACK = 1 << 1;
1082        const EXEC_LOCATION = 1 << 2;
1083        const GAS_REMAINING = 1 << 3;
1084        const GAS_CONSUMED = 1 << 4;
1085        const DUMP_STACK_VERBOSE = 1 << 5;
1086        const DUMP_C5 = 1 << 6;
1087
1088        const FULL = 0b111111;
1089    }
1090}
1091
1092/// Execution effects.
1093pub struct CommittedState {
1094    /// Contract data.
1095    pub c4: Cell,
1096    /// Result action list.
1097    pub c5: Cell,
1098}
1099
1100bitflags! {
1101    /// A mask to specify which control registers are saved.
1102    pub struct SaveCr: u8 {
1103        const NONE = 0;
1104
1105        const C0 = 1;
1106        const C1 = 1 << 1;
1107        const C2 = 1 << 2;
1108
1109        const C0_C1 = SaveCr::C0.bits() | SaveCr::C1.bits();
1110        const FULL = SaveCr::C0_C1.bits() | SaveCr::C2.bits();
1111    }
1112}
1113
1114thread_local! {
1115    pub(crate) static QUIT0: SafeRc<QuitCont> = SafeRc::new(QuitCont { exit_code: 0 });
1116    pub(crate) static QUIT1: SafeRc<QuitCont> = SafeRc::new(QuitCont { exit_code: 1 });
1117    pub(crate) static QUIT11: SafeRc<QuitCont> = SafeRc::new(QuitCont { exit_code: 11 });
1118    pub(crate) static EXC_QUIT: SafeRc<ExcQuitCont> = SafeRc::new(ExcQuitCont);
1119}