Skip to main content

zksync_vm2/
program.rs

1use std::{fmt, sync::Arc};
2
3use primitive_types::U256;
4use zksync_vm2_interface::Tracer;
5
6use crate::{
7    addressing_modes::Arguments, decode::decode, hash_for_debugging, instruction::ExecutionStatus,
8    Instruction, ModeRequirements, Predicate, VirtualMachine, World,
9};
10
11/// Compiled EraVM bytecode.
12///
13/// Cloning this is cheap. It is a handle to memory similar to [`Arc`].
14pub struct Program<T, W> {
15    // An internal representation that doesn't need two Arcs would be better
16    // but it would also require a lot of unsafe, so I made this wrapper to
17    // enable changing the internals later.
18    code_page: Arc<[U256]>,
19    instructions: Arc<[Instruction<T, W>]>,
20}
21
22impl<T, W> Clone for Program<T, W> {
23    fn clone(&self) -> Self {
24        Self {
25            code_page: self.code_page.clone(),
26            instructions: self.instructions.clone(),
27        }
28    }
29}
30
31impl<T, W> fmt::Debug for Program<T, W> {
32    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
33        const DEBUGGED_ITEMS: usize = 16;
34
35        let mut s = formatter.debug_struct("Program");
36        if self.code_page.len() <= DEBUGGED_ITEMS {
37            s.field("code_page", &self.code_page);
38        } else {
39            s.field("code_page.len", &self.code_page.len())
40                .field("code_page.start", &&self.code_page[..DEBUGGED_ITEMS])
41                .field("code_page.hash", &hash_for_debugging(&self.code_page));
42        }
43
44        if self.instructions.len() <= DEBUGGED_ITEMS {
45            s.field("instructions", &self.instructions);
46        } else {
47            s.field("instructions.len", &self.instructions.len())
48                .field("instructions.start", &&self.instructions[..DEBUGGED_ITEMS]);
49        }
50        s.finish_non_exhaustive()
51    }
52}
53
54impl<T: Tracer, W: World<T>> Program<T, W> {
55    /// Creates a new program.
56    #[allow(clippy::missing_panics_doc)] // false positive
57    pub fn new(bytecode: &[u8], enable_hooks: bool) -> Self {
58        let instructions = decode_program(
59            &bytecode
60                .chunks_exact(8)
61                .map(|chunk| u64::from_be_bytes(chunk.try_into().unwrap()))
62                .collect::<Vec<_>>(),
63            enable_hooks,
64        );
65        let code_page = bytecode
66            .chunks_exact(32)
67            .map(U256::from_big_endian)
68            .collect::<Vec<_>>();
69        Self {
70            instructions: instructions.into(),
71            code_page: code_page.into(),
72        }
73    }
74
75    /// Creates a new program from `U256` words.
76    pub fn from_words(bytecode_words: Vec<U256>, enable_hooks: bool) -> Self {
77        let instructions = decode_program(
78            &bytecode_words
79                .iter()
80                .flat_map(|x| x.0.into_iter().rev())
81                .collect::<Vec<_>>(),
82            enable_hooks,
83        );
84        Self {
85            instructions: instructions.into(),
86            code_page: bytecode_words.into(),
87        }
88    }
89
90    pub(crate) fn new_panicking() -> Self {
91        Self::from_raw(vec![Instruction::from_spontaneous_panic()], vec![])
92    }
93
94    #[doc(hidden)] // should only be used in low-level tests / benchmarks
95    pub fn from_raw(instructions: Vec<Instruction<T, W>>, code_page: Vec<U256>) -> Self {
96        Self {
97            instructions: instructions.into(),
98            code_page: code_page.into(),
99        }
100    }
101}
102
103impl<T, W> Program<T, W> {
104    pub(crate) fn instruction(&self, n: u16) -> Option<&Instruction<T, W>> {
105        self.instructions.get::<usize>(n.into())
106    }
107
108    /// Returns a reference to the code page of this program.
109    pub fn code_page(&self) -> &[U256] {
110        &self.code_page
111    }
112}
113
114// This implementation compares pointers instead of programs.
115//
116// That works well enough for the tests that this is written for.
117// I don't want to implement PartialEq for Instruction because
118// comparing function pointers can work in suprising ways.
119impl<T, W> PartialEq for Program<T, W> {
120    fn eq(&self, other: &Self) -> bool {
121        Arc::ptr_eq(&self.code_page, &other.code_page)
122            && Arc::ptr_eq(&self.instructions, &other.instructions)
123    }
124}
125
126/// Wraparound instruction placed at the end of programs exceeding `1 << 16` instructions to simulate the 16-bit program counter overflowing.
127/// Does not invoke tracers because it is an implementation detail, not an actual instruction.
128fn jump_to_beginning<T, W>() -> Instruction<T, W> {
129    Instruction {
130        handler: jump_to_beginning_handler,
131        arguments: Arguments::new(Predicate::Always, 0, ModeRequirements::none()),
132    }
133}
134
135fn jump_to_beginning_handler<T, W>(
136    vm: &mut VirtualMachine<T, W>,
137    _: &mut W,
138    _: &mut T,
139) -> ExecutionStatus {
140    let first_instruction = vm.state.current_frame.program.instruction(0).unwrap();
141    vm.state.current_frame.pc = first_instruction;
142    ExecutionStatus::Running
143}
144
145fn decode_program<T: Tracer, W: World<T>>(
146    raw: &[u64],
147    is_bootloader: bool,
148) -> Vec<Instruction<T, W>> {
149    raw.iter()
150        .take(1 << 16)
151        .map(|i| decode(*i, is_bootloader))
152        .chain(std::iter::once(if raw.len() >= 1 << 16 {
153            jump_to_beginning()
154        } else {
155            Instruction::from_invalid()
156        }))
157        .collect()
158}