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    pub fn new(bytecode: &[u8], enable_hooks: bool) -> Self {
57        let (words, _) = bytecode.as_chunks::<8>();
58        let instructions = decode_program(
59            &words
60                .iter()
61                .copied()
62                .map(u64::from_be_bytes)
63                .collect::<Vec<_>>(),
64            enable_hooks,
65        );
66        let (cells, _) = bytecode.as_chunks::<32>();
67        let code_page = cells
68            .iter()
69            .map(|cell| U256::from_big_endian(cell))
70            .collect::<Vec<_>>();
71        Self {
72            instructions: instructions.into(),
73            code_page: code_page.into(),
74        }
75    }
76
77    /// Creates a new program from `U256` words.
78    pub fn from_words(bytecode_words: Vec<U256>, enable_hooks: bool) -> Self {
79        let instructions = decode_program(
80            &bytecode_words
81                .iter()
82                .flat_map(|x| x.0.into_iter().rev())
83                .collect::<Vec<_>>(),
84            enable_hooks,
85        );
86        Self {
87            instructions: instructions.into(),
88            code_page: bytecode_words.into(),
89        }
90    }
91
92    pub(crate) fn new_panicking() -> Self {
93        Self::from_raw(vec![Instruction::from_spontaneous_panic()], vec![])
94    }
95
96    #[doc(hidden)] // should only be used in low-level tests / benchmarks
97    pub fn from_raw(instructions: Vec<Instruction<T, W>>, code_page: Vec<U256>) -> Self {
98        Self {
99            instructions: instructions.into(),
100            code_page: code_page.into(),
101        }
102    }
103}
104
105impl<T, W> Program<T, W> {
106    pub(crate) fn instruction(&self, n: u16) -> Option<&Instruction<T, W>> {
107        self.instructions.get::<usize>(n.into())
108    }
109
110    /// Returns a reference to the code page of this program.
111    pub fn code_page(&self) -> &[U256] {
112        &self.code_page
113    }
114}
115
116// This implementation compares pointers instead of programs.
117//
118// That works well enough for the tests that this is written for.
119// I don't want to implement PartialEq for Instruction because
120// comparing function pointers can work in suprising ways.
121impl<T, W> PartialEq for Program<T, W> {
122    fn eq(&self, other: &Self) -> bool {
123        Arc::ptr_eq(&self.code_page, &other.code_page)
124            && Arc::ptr_eq(&self.instructions, &other.instructions)
125    }
126}
127
128/// Wraparound instruction placed at the end of programs exceeding `1 << 16` instructions to simulate the 16-bit program counter overflowing.
129/// Does not invoke tracers because it is an implementation detail, not an actual instruction.
130fn jump_to_beginning<T, W>() -> Instruction<T, W> {
131    Instruction {
132        handler: jump_to_beginning_handler,
133        arguments: Arguments::new(Predicate::Always, 0, ModeRequirements::none()),
134    }
135}
136
137fn jump_to_beginning_handler<T, W>(
138    vm: &mut VirtualMachine<T, W>,
139    _: &mut W,
140    _: &mut T,
141) -> ExecutionStatus {
142    let first_instruction = vm.state.current_frame.program.instruction(0).unwrap();
143    vm.state.current_frame.pc = first_instruction;
144    ExecutionStatus::Running
145}
146
147fn decode_program<T: Tracer, W: World<T>>(
148    raw: &[u64],
149    is_bootloader: bool,
150) -> Vec<Instruction<T, W>> {
151    raw.iter()
152        .take(1 << 16)
153        .map(|i| decode(*i, is_bootloader))
154        .chain(std::iter::once(if raw.len() >= 1 << 16 {
155            jump_to_beginning()
156        } else {
157            Instruction::from_invalid()
158        }))
159        .collect()
160}