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#[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
156pub 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 self = CellBuilder::build_from(self).unwrap();
191 } else {
192 return Err(Error::UnexpectedExoticCell);
194 }
195 }
196
197 Ok(OwnedCellSlice::new_allow_exotic(self))
198 }
199}
200
201#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
203pub enum InitSelectorParams {
204 #[default]
205 None,
206 UseCode {
207 push0: bool,
208 },
209}
210
211pub 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
229pub struct ParentVmState<'a> {
231 pub code: OwnedCellSlice,
233 pub stack: SafeRc<Stack>,
235 pub cr: ControlRegs,
237 pub committed_state: Option<CommittedState>,
239 pub steps: u64,
241 pub quit0: SafeRc<QuitCont>,
243 pub quit1: SafeRc<QuitCont>,
245 pub gas: ParentGasConsumer<'a>,
247 pub cp: &'static DispatchTable,
249
250 pub return_data: bool,
252 pub return_actions: bool,
254 pub return_gas: bool,
256 pub return_values: Option<u32>,
260
261 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 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 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 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 return self.jump(cont);
590 }
591 if control_data.stack.is_some() || control_data.nargs.is_some() {
592 return self.call_ext(cont, None, None);
595 }
596 }
597
598 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 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 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 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 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 return self.jump_ext(cont, None);
715 }
716 }
717
718 self.do_jump_to(cont)
724 }
725
726 pub fn jump_ext(&mut self, cont: RcCont, pass_args: Option<u16>) -> VmResult<i32> {
727 let cont = ok!(self.adjust_jump_cont(cont, pass_args));
731
732 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 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 self.preclear_cr(&control_data.save);
771
772 let next_depth = control_data
774 .nargs
775 .or(pass_args)
776 .map(|n| n as usize)
777 .unwrap_or(current_depth);
778
779 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 Some(mut cont_stack) if !cont_stack.items.is_empty() => {
792 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 _ 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 _ => {}
806 }
807 } else if let Some(pass_args) = pass_args {
808 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 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 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 = 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 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 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#[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 #[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
1064pub struct CommittedState {
1066 pub c4: Cell,
1068 pub c5: Cell,
1070}
1071
1072bitflags! {
1073 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}