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