1use std::collections::BTreeMap;
2
3use anyhow::Result;
4use tycho_types::prelude::*;
5
6use crate::error::VmResult;
7#[cfg(feature = "dump")]
8use crate::error::{DumpError, DumpResult};
9use crate::state::VmState;
10
11pub trait OpcodeBase: Send + Sync {
12 fn range(&self) -> (u32, u32);
14}
15
16pub trait OpcodeExec: OpcodeBase {
18 fn exec(&self, st: &mut VmState, opcode: u32, bits: u16) -> VmResult<i32>;
20}
21
22#[cfg(feature = "dump")]
24pub trait OpcodeDump: OpcodeBase {
25 fn dump(
27 &self,
28 code: &mut CellSlice<'_>,
29 opcode: u32,
30 bits: u16,
31 f: &mut dyn DumpOutput,
32 ) -> DumpResult;
33}
34
35#[cfg(feature = "dump")]
37pub trait DumpOutput {
38 fn record_gas(&mut self, gas: u64) -> DumpResult;
39 fn record_opcode(&mut self, value: &dyn std::fmt::Display) -> DumpResult;
40 fn record_cell(&mut self, value: Cell) -> DumpResult;
41 fn record_slice(&mut self, value: CellSlice<'_>) -> DumpResult;
42 fn record_cont(&mut self, cont: Cell) -> DumpResult;
43 fn record_cont_slice(&mut self, cont: CellSlice<'_>) -> DumpResult;
44 fn record_dict(&mut self, n: u16, slice: CellSlice<'_>) -> DumpResult;
45}
46
47pub struct DispatchTable {
49 id: u16,
50 exec_opcodes: Vec<(u32, Box<dyn OpcodeExec>)>,
51 #[cfg(feature = "dump")]
52 dump_opcodes: Vec<(u32, Box<dyn OpcodeDump>)>,
53}
54
55impl DispatchTable {
56 pub fn builder(id: u16) -> Opcodes {
57 Opcodes {
58 id,
59 exec_opcodes: Default::default(),
60 #[cfg(feature = "dump")]
61 dump_opcodes: Default::default(),
62 }
63 }
64
65 #[inline]
66 pub fn id(&self) -> u16 {
67 self.id
68 }
69
70 pub fn lookup(&self, opcode: u32) -> &dyn OpcodeExec {
71 Self::lookup_impl(opcode, &self.exec_opcodes)
72 }
73
74 pub fn dispatch(&self, st: &mut VmState) -> VmResult<i32> {
75 let (opcode, bits) = Self::get_opcode_from_slice(&st.code.apply());
76 let op = self.lookup(opcode);
77 op.exec(st, opcode, bits)
78 }
79
80 #[cfg(feature = "dump")]
81 pub fn dispatch_dump(&self, code: &mut CellSlice<'_>, f: &mut dyn DumpOutput) -> DumpResult {
82 let (opcode, bits) = Self::get_opcode_from_slice(code);
83 let op = Self::lookup_impl(opcode, &self.dump_opcodes);
84 op.dump(code, opcode, bits, f)
85 }
86
87 fn get_opcode_from_slice(slice: &CellSlice<'_>) -> (u32, u16) {
88 let bits = std::cmp::min(MAX_OPCODE_BITS, slice.size_bits());
89 let opcode = (slice.get_uint(0, bits).unwrap() as u32) << (MAX_OPCODE_BITS - bits);
90 (opcode, bits)
91 }
92
93 #[inline]
94 fn lookup_impl<T, V>(opcode: u32, opcodes: &[(u32, T)]) -> &V
95 where
96 T: AsRef<V>,
97 V: ?Sized,
98 {
99 debug_assert!(!opcodes.is_empty());
100
101 let mut i = 0;
102 let mut j = opcodes.len();
103 while j - i > 1 {
104 let k = (j + i) >> 1;
105 if opcodes[k].0 <= opcode {
106 i = k;
107 } else {
108 j = k;
109 }
110 }
111 opcodes[i].1.as_ref()
112 }
113}
114
115pub struct Opcodes {
117 id: u16,
118 exec_opcodes: BTreeMap<u32, Box<dyn OpcodeExec>>,
119 #[cfg(feature = "dump")]
120 dump_opcodes: BTreeMap<u32, Box<dyn OpcodeDump>>,
121}
122
123impl Opcodes {
124 pub fn build(self) -> DispatchTable {
125 let exec_opcodes = build_opcodes(self.exec_opcodes, |min, max| {
126 Box::new(DummyOpcode {
127 opcode_min: min,
128 opcode_max: max,
129 })
130 });
131
132 #[cfg(feature = "dump")]
133 let dump_opcodes = build_opcodes(self.dump_opcodes, |min, max| {
134 Box::new(DummyOpcode {
135 opcode_min: min,
136 opcode_max: max,
137 })
138 });
139
140 DispatchTable {
141 id: self.id,
142 exec_opcodes,
143 #[cfg(feature = "dump")]
144 dump_opcodes,
145 }
146 }
147
148 pub fn add_simple(
149 &mut self,
150 opcode: u32,
151 bits: u16,
152 exec: FnExecInstrSimple,
153 #[cfg(feature = "dump")] dump: FnDumpInstrSimple,
154 ) -> Result<()> {
155 let remaining_bits = MAX_OPCODE_BITS - bits;
156
157 #[cfg(feature = "dump")]
158 self.add_dump_opcode(Box::new(SimpleOpcode {
159 f: dump,
160 opcode_min: opcode << remaining_bits,
161 opcode_max: (opcode + 1) << remaining_bits,
162 opcode_bits: bits,
163 }))?;
164
165 self.add_opcode(Box::new(SimpleOpcode {
166 f: exec,
167 opcode_min: opcode << remaining_bits,
168 opcode_max: (opcode + 1) << remaining_bits,
169 opcode_bits: bits,
170 }))
171 }
172
173 pub fn add_fixed(
174 &mut self,
175 opcode: u32,
176 opcode_bits: u16,
177 arg_bits: u16,
178 exec: FnExecInstrArg,
179 #[cfg(feature = "dump")] dump: FnDumpInstrArg,
180 ) -> Result<()> {
181 let remaining_bits = MAX_OPCODE_BITS - opcode_bits;
182
183 #[cfg(feature = "dump")]
184 self.add_dump_opcode(Box::new(FixedOpcode {
185 f: dump,
186 opcode_min: opcode << remaining_bits,
187 opcode_max: (opcode + 1) << remaining_bits,
188 total_bits: opcode_bits + arg_bits,
189 }))?;
190
191 self.add_opcode(Box::new(FixedOpcode {
192 f: exec,
193 opcode_min: opcode << remaining_bits,
194 opcode_max: (opcode + 1) << remaining_bits,
195 total_bits: opcode_bits + arg_bits,
196 }))
197 }
198
199 pub fn add_fixed_range(
200 &mut self,
201 opcode_min: u32,
202 opcode_max: u32,
203 total_bits: u16,
204 _arg_bits: u16,
205 exec: FnExecInstrArg,
206 #[cfg(feature = "dump")] dump: FnDumpInstrArg,
207 ) -> Result<()> {
208 let remaining_bits = MAX_OPCODE_BITS - total_bits;
209
210 #[cfg(feature = "dump")]
211 self.add_dump_opcode(Box::new(FixedOpcode {
212 f: dump,
213 opcode_min: opcode_min << remaining_bits,
214 opcode_max: opcode_max << remaining_bits,
215 total_bits,
216 }))?;
217
218 self.add_opcode(Box::new(FixedOpcode {
219 f: exec,
220 opcode_min: opcode_min << remaining_bits,
221 opcode_max: opcode_max << remaining_bits,
222 total_bits,
223 }))
224 }
225
226 pub fn add_ext(
227 &mut self,
228 opcode: u32,
229 opcode_bits: u16,
230 arg_bits: u16,
231 exec: FnExecInstrFull,
232 #[cfg(feature = "dump")] dump: FnDumpInstrFull,
233 ) -> Result<()> {
234 let remaining_bits = MAX_OPCODE_BITS - opcode_bits;
235
236 #[cfg(feature = "dump")]
237 self.add_dump_opcode(Box::new(ExtOpcode {
238 f: dump,
239 opcode_min: opcode << remaining_bits,
240 opcode_max: (opcode + 1) << remaining_bits,
241 total_bits: opcode_bits + arg_bits,
242 }))?;
243
244 self.add_opcode(Box::new(ExtOpcode {
245 f: exec,
246 opcode_min: opcode << remaining_bits,
247 opcode_max: (opcode + 1) << remaining_bits,
248 total_bits: opcode_bits + arg_bits,
249 }))
250 }
251
252 pub fn add_ext_range(
253 &mut self,
254 opcode_min: u32,
255 opcode_max: u32,
256 total_bits: u16,
257 exec: FnExecInstrFull,
258 #[cfg(feature = "dump")] dump: FnDumpInstrFull,
259 ) -> Result<()> {
260 let remaining_bits = MAX_OPCODE_BITS - total_bits;
261
262 #[cfg(feature = "dump")]
263 self.add_dump_opcode(Box::new(ExtOpcode {
264 f: dump,
265 opcode_min: opcode_min << remaining_bits,
266 opcode_max: opcode_max << remaining_bits,
267 total_bits,
268 }))?;
269
270 self.add_opcode(Box::new(ExtOpcode {
271 f: exec,
272 opcode_min: opcode_min << remaining_bits,
273 opcode_max: opcode_max << remaining_bits,
274 total_bits,
275 }))
276 }
277
278 pub fn add_opcode(&mut self, opcode: Box<dyn OpcodeExec>) -> Result<()> {
279 Self::add_opcode_impl(opcode, &mut self.exec_opcodes)
280 }
281
282 #[cfg(feature = "dump")]
283 pub fn add_dump_opcode(&mut self, opcode: Box<dyn OpcodeDump>) -> Result<()> {
284 Self::add_opcode_impl(opcode, &mut self.dump_opcodes)
285 }
286
287 #[inline]
288 fn add_opcode_impl<T: OpcodeBase + ?Sized>(
289 opcode: Box<T>,
290 opcodes: &mut BTreeMap<u32, Box<T>>,
291 ) -> Result<()> {
292 let (min, max) = opcode.range();
293 debug_assert!(min < max);
294 debug_assert!(max <= MAX_OPCODE);
295
296 if let Some((other_min, _)) = opcodes.range(min..).next() {
297 anyhow::ensure!(
298 max <= *other_min,
299 "Opcode overlaps with next min: {other_min:06x}"
300 );
301 }
302
303 if let Some((k, prev)) = opcodes.range(..=min).next_back() {
304 let (prev_min, prev_max) = prev.range();
305 debug_assert!(prev_min < prev_max);
306 debug_assert!(prev_min == *k);
307 anyhow::ensure!(
308 prev_max <= min,
309 "Opcode overlaps with prev max: {prev_max:06x}"
310 );
311 }
312
313 opcodes.insert(min, opcode);
314 Ok(())
315 }
316}
317
318fn build_opcodes<T, F>(items: BTreeMap<u32, Box<T>>, f: F) -> Vec<(u32, Box<T>)>
319where
320 T: OpcodeBase + ?Sized,
321 F: Fn(u32, u32) -> Box<T>,
322{
323 let mut opcodes = Vec::with_capacity(items.len() * 2 + 1);
324
325 let mut upto = 0;
326 for (k, opcode) in items {
327 let (min, max) = opcode.range();
328 if min > upto {
329 opcodes.push((upto, f(upto, min)));
330 }
331
332 opcodes.push((k, opcode));
333 upto = max;
334 }
335
336 if upto < MAX_OPCODE {
337 opcodes.push((upto, f(upto, MAX_OPCODE)));
338 }
339
340 opcodes.shrink_to_fit();
341 opcodes
342}
343
344struct DummyOpcode {
347 opcode_min: u32,
348 opcode_max: u32,
349}
350
351impl OpcodeBase for DummyOpcode {
352 fn range(&self) -> (u32, u32) {
353 (self.opcode_min, self.opcode_max)
354 }
355}
356
357impl OpcodeExec for DummyOpcode {
358 fn exec(&self, st: &mut VmState, _: u32, _: u16) -> VmResult<i32> {
359 st.gas.try_consume(GAS_PER_INSTRUCTION)?;
360 vm_bail!(InvalidOpcode);
361 }
362}
363
364#[cfg(feature = "dump")]
365impl OpcodeDump for DummyOpcode {
366 fn dump(&self, _: &mut CellSlice<'_>, _: u32, _: u16, _: &mut dyn DumpOutput) -> DumpResult {
367 Err(DumpError::InvalidOpcode)
368 }
369}
370
371struct SimpleOpcode<T> {
372 f: T,
373 opcode_min: u32,
374 opcode_max: u32,
375 opcode_bits: u16,
376}
377
378impl<T: Send + Sync> OpcodeBase for SimpleOpcode<T> {
379 fn range(&self) -> (u32, u32) {
380 (self.opcode_min, self.opcode_max)
381 }
382}
383
384impl OpcodeExec for SimpleOpcode<FnExecInstrSimple> {
385 fn exec(&self, st: &mut VmState, _: u32, bits: u16) -> VmResult<i32> {
386 st.gas.try_consume(opcode_gas(self.opcode_bits))?;
387 vm_ensure!(bits >= self.opcode_bits, InvalidOpcode);
388 st.code.range_mut().skip_first(self.opcode_bits, 0)?;
389 (self.f)(st)
390 }
391}
392
393#[cfg(feature = "dump")]
394impl OpcodeDump for SimpleOpcode<FnDumpInstrSimple> {
395 fn dump(
396 &self,
397 code: &mut CellSlice<'_>,
398 _: u32,
399 bits: u16,
400 f: &mut dyn DumpOutput,
401 ) -> DumpResult {
402 if bits >= self.opcode_bits {
403 f.record_gas(opcode_gas(self.opcode_bits))?;
404 code.skip_first(self.opcode_bits, 0)?;
405 (self.f)(f)
406 } else {
407 Err(DumpError::InvalidOpcode)
408 }
409 }
410}
411
412struct FixedOpcode<T> {
413 f: T,
414 opcode_min: u32,
415 opcode_max: u32,
416 total_bits: u16,
417}
418
419impl<T: Send + Sync> OpcodeBase for FixedOpcode<T> {
420 fn range(&self) -> (u32, u32) {
421 (self.opcode_min, self.opcode_max)
422 }
423}
424
425impl OpcodeExec for FixedOpcode<FnExecInstrArg> {
426 fn exec(&self, st: &mut VmState, opcode: u32, bits: u16) -> VmResult<i32> {
427 st.gas.try_consume(opcode_gas(self.total_bits))?;
428 vm_ensure!(bits >= self.total_bits, InvalidOpcode);
429 st.code.range_mut().skip_first(self.total_bits, 0)?;
430 (self.f)(st, opcode >> (MAX_OPCODE_BITS - self.total_bits))
431 }
432}
433
434#[cfg(feature = "dump")]
435impl OpcodeDump for FixedOpcode<FnDumpInstrArg> {
436 fn dump(
437 &self,
438 code: &mut CellSlice<'_>,
439 opcode: u32,
440 bits: u16,
441 f: &mut dyn DumpOutput,
442 ) -> DumpResult {
443 if bits >= self.total_bits {
444 f.record_gas(opcode_gas(self.total_bits))?;
445 code.skip_first(self.total_bits, 0)?;
446 (self.f)(opcode >> (MAX_OPCODE_BITS - self.total_bits), f)
447 } else {
448 Err(DumpError::InvalidOpcode)
449 }
450 }
451}
452
453struct ExtOpcode<T> {
454 f: T,
455 opcode_min: u32,
456 opcode_max: u32,
457 total_bits: u16,
458}
459
460impl<T: Send + Sync> OpcodeBase for ExtOpcode<T> {
461 fn range(&self) -> (u32, u32) {
462 (self.opcode_min, self.opcode_max)
463 }
464}
465
466impl OpcodeExec for ExtOpcode<FnExecInstrFull> {
467 fn exec(&self, st: &mut VmState, opcode: u32, bits: u16) -> VmResult<i32> {
468 st.gas.try_consume(opcode_gas(self.total_bits))?;
469 vm_ensure!(bits >= self.total_bits, InvalidOpcode);
470 (self.f)(
471 st,
472 opcode >> (MAX_OPCODE_BITS - self.total_bits),
473 self.total_bits,
474 )
475 }
476}
477
478#[cfg(feature = "dump")]
479impl OpcodeDump for ExtOpcode<FnDumpInstrFull> {
480 fn dump(
481 &self,
482 code: &mut CellSlice<'_>,
483 opcode: u32,
484 bits: u16,
485 f: &mut dyn DumpOutput,
486 ) -> DumpResult {
487 if bits >= self.total_bits {
488 f.record_gas(opcode_gas(self.total_bits))?;
489 (self.f)(
490 code,
491 opcode >> (MAX_OPCODE_BITS - self.total_bits),
492 self.total_bits,
493 f,
494 )
495 } else {
496 Err(DumpError::InvalidOpcode)
497 }
498 }
499}
500
501const fn opcode_gas(bits: u16) -> u64 {
502 GAS_PER_INSTRUCTION + bits as u64 * GAS_PER_BIT
503}
504
505pub type FnExecInstrSimple = fn(&mut VmState) -> VmResult<i32>;
507
508#[cfg(feature = "dump")]
510pub type FnDumpInstrSimple = fn(&mut dyn DumpOutput) -> DumpResult;
511
512pub type FnExecInstrArg = fn(&mut VmState, u32) -> VmResult<i32>;
514
515#[cfg(feature = "dump")]
517pub type FnDumpInstrArg = fn(u32, &mut dyn DumpOutput) -> DumpResult;
518
519pub type FnExecInstrFull = fn(&mut VmState, u32, u16) -> VmResult<i32>;
521
522#[cfg(feature = "dump")]
524pub type FnDumpInstrFull = fn(&mut CellSlice<'_>, u32, u16, &mut dyn DumpOutput) -> DumpResult;
525
526const MAX_OPCODE_BITS: u16 = 24;
527const MAX_OPCODE: u32 = 1 << MAX_OPCODE_BITS;
528
529const GAS_PER_INSTRUCTION: u64 = 10;
530const GAS_PER_BIT: u64 = 1;
531
532#[cfg(test)]
533mod tests {
534 use super::*;
535 use crate::cont::QuitCont;
536 use crate::error::VmError;
537 use crate::gas::{GasConsumer, GasParams};
538 use crate::saferc::SafeRc;
539 use crate::smc_info::VmVersion;
540
541 #[test]
542 fn dummy_codepage() {
543 let cp = DispatchTable::builder(123).build();
544
545 let mut state = VmState {
546 code: Default::default(),
547 throw_on_code_access: false,
548 stack: Default::default(),
549 signature_domains: Default::default(),
550 cr: Default::default(),
551 committed_state: Default::default(),
552 steps: 0,
553 quit0: SafeRc::from(QuitCont { exit_code: 0 }),
554 quit1: SafeRc::from(QuitCont { exit_code: 0 }),
555 gas: GasConsumer::new(GasParams::getter()),
556 cp: Box::leak(Box::new(cp)),
557 debug: None,
558 modifiers: Default::default(),
559 version: VmVersion::LATEST_TON,
560 parent: None,
561 };
562
563 let dummy = state.cp.lookup(0x800000);
564 assert_eq!(dummy.range(), (0x000000, 0x1000000));
565
566 let err = dummy.exec(&mut state, 0x800000, 24).unwrap_err();
567 assert!(matches!(*err, VmError::InvalidOpcode));
568 }
569
570 #[test]
571 fn opcode_overlap_check_works() {
572 {
574 let mut cp = DispatchTable::builder(123);
575 cp.add_simple(
576 0xab,
577 8,
578 |_| Ok(0),
579 #[cfg(feature = "dump")]
580 |_| Ok(()),
581 )
582 .unwrap();
583 cp.add_simple(
584 0xab,
585 8,
586 |_| Ok(0),
587 #[cfg(feature = "dump")]
588 |_| Ok(()),
589 )
590 .unwrap_err();
591 }
592
593 {
595 let mut cp = DispatchTable::builder(123);
596 cp.add_simple(
597 0xab,
598 8,
599 |_| Ok(0),
600 #[cfg(feature = "dump")]
601 |_| Ok(()),
602 )
603 .unwrap();
604 cp.add_fixed_range(
605 0xa0,
606 0xaf,
607 8,
608 4,
609 |_, _| Ok(0),
610 #[cfg(feature = "dump")]
611 |_, _| Ok(()),
612 )
613 .unwrap_err();
614 }
615
616 {
618 let mut cp = DispatchTable::builder(123);
619 cp.add_fixed_range(
620 0xa0,
621 0xaf,
622 8,
623 4,
624 |_, _| Ok(0),
625 #[cfg(feature = "dump")]
626 |_, _| Ok(()),
627 )
628 .unwrap();
629 cp.add_simple(
630 0xab,
631 8,
632 |_| Ok(0),
633 #[cfg(feature = "dump")]
634 |_| Ok(()),
635 )
636 .unwrap_err();
637 }
638
639 {
641 let mut cp = DispatchTable::builder(123);
642 cp.add_fixed_range(
643 0xa0,
644 0xaf,
645 8,
646 4,
647 |_, _| Ok(0),
648 #[cfg(feature = "dump")]
649 |_, _| Ok(()),
650 )
651 .unwrap();
652 cp.add_fixed_range(
653 0xa4,
654 0xa7,
655 8,
656 2,
657 |_, _| Ok(0),
658 #[cfg(feature = "dump")]
659 |_, _| Ok(()),
660 )
661 .unwrap_err();
662 }
663 }
664}