Skip to main content

zksync_vm2/
addressing_modes.rs

1//! Addressing modes supported by EraVM.
2
3#[cfg(feature = "arbitrary")]
4use arbitrary::{Arbitrary, Unstructured};
5use enum_dispatch::enum_dispatch;
6use primitive_types::U256;
7use zkevm_opcode_defs::erase_fat_pointer_metadata;
8
9use crate::{mode_requirements::ModeRequirements, predication::Predicate};
10
11pub(crate) trait Source {
12    /// Get a word's value for non-pointer operations. (Pointers are erased.)
13    fn get(args: &Arguments, state: &mut impl Addressable) -> U256 {
14        Self::get_with_pointer_flag_and_erasing(args, state).0
15    }
16
17    /// Get a word's value and pointer flag.
18    fn get_with_pointer_flag(args: &Arguments, state: &mut impl Addressable) -> (U256, bool) {
19        (Self::get(args, state), false)
20    }
21
22    /// Get a word's value, erasing pointers but also returning the pointer flag.
23    /// The flag will always be false unless in kernel mode.
24    /// Necessary for pointer operations, which for some reason erase their second argument
25    /// but also panic when it was a pointer.
26    fn get_with_pointer_flag_and_erasing(
27        args: &Arguments,
28        state: &mut impl Addressable,
29    ) -> (U256, bool) {
30        let (mut value, is_pointer) = Self::get_with_pointer_flag(args, state);
31        if is_pointer && !state.in_kernel_mode() {
32            erase_fat_pointer_metadata(&mut value);
33        }
34        (value, is_pointer && state.in_kernel_mode())
35    }
36}
37
38pub(crate) trait Destination {
39    /// Set this register/stack location to value and clear its pointer flag
40    fn set(args: &Arguments, state: &mut impl Addressable, value: U256);
41
42    /// Same as `set` but sets the pointer flag
43    fn set_fat_ptr(args: &Arguments, state: &mut impl Addressable, value: U256);
44}
45
46/// The part of VM state that addressing modes need to operate on
47pub(crate) trait Addressable {
48    fn registers(&mut self) -> &mut [U256; 16];
49    fn register_pointer_flags(&mut self) -> &mut u16;
50
51    fn read_stack(&mut self, slot: u16) -> U256;
52    fn write_stack(&mut self, slot: u16, value: U256);
53    fn stack_pointer(&mut self) -> &mut u16;
54
55    fn read_stack_pointer_flag(&mut self, slot: u16) -> bool;
56    fn set_stack_pointer_flag(&mut self, slot: u16);
57    fn clear_stack_pointer_flag(&mut self, slot: u16);
58
59    /// Records that the current instruction wrote its second output (`dst1`) register, so that
60    /// `full_boilerplate` does not clear it afterwards. Called by every `dst1` write.
61    fn mark_dst1_written(&mut self);
62
63    fn code_page(&self) -> &[U256];
64
65    fn in_kernel_mode(&self) -> bool;
66}
67
68#[enum_dispatch]
69pub(crate) trait SourceWriter {
70    fn write_source(&self, args: &mut Arguments);
71}
72
73impl<T: SourceWriter> SourceWriter for Option<T> {
74    fn write_source(&self, args: &mut Arguments) {
75        if let Some(x) = self {
76            x.write_source(args);
77        }
78    }
79}
80
81#[enum_dispatch]
82pub(crate) trait DestinationWriter {
83    fn write_destination(&self, args: &mut Arguments);
84}
85
86impl<T: DestinationWriter> DestinationWriter for Option<T> {
87    fn write_destination(&self, args: &mut Arguments) {
88        if let Some(x) = self {
89            x.write_destination(args);
90        }
91    }
92}
93
94/// Arguments provided to an instruction in an EraVM bytecode.
95// It is important for performance that this fits into 8 bytes.
96#[derive(Debug)]
97pub struct Arguments {
98    source_registers: PackedRegisters,
99    destination_registers: PackedRegisters,
100    immediate1: u16,
101    immediate2: u16,
102    predicate_and_mode_requirements: u8,
103    static_gas_cost: u8,
104}
105
106pub(crate) const L1_MESSAGE_COST: u32 = 156_250;
107pub(crate) const SSTORE_COST: u32 = 5_511;
108pub(crate) const SLOAD_COST: u32 = 2_008;
109pub(crate) const INVALID_INSTRUCTION_COST: u32 = 4_294_967_295;
110
111impl Arguments {
112    /// Creates arguments from the provided info.
113    #[allow(clippy::missing_panics_doc)] // never panics on properly created inputs
114    pub const fn new(
115        predicate: Predicate,
116        gas_cost: u32,
117        mode_requirements: ModeRequirements,
118    ) -> Self {
119        // Make sure that these two can be packed into 8 bits without overlapping
120        assert!(predicate as u8 & (0b11 << 6) == 0);
121        assert!(mode_requirements.0 & !0b11 == 0);
122
123        Self {
124            source_registers: PackedRegisters(0),
125            destination_registers: PackedRegisters(0),
126            immediate1: 0,
127            immediate2: 0,
128            predicate_and_mode_requirements: ((predicate as u8) << 2) | mode_requirements.0,
129            static_gas_cost: Self::encode_static_gas_cost(gas_cost),
130        }
131    }
132
133    #[allow(clippy::cast_possible_truncation)] // checked
134    const fn encode_static_gas_cost(x: u32) -> u8 {
135        match x {
136            L1_MESSAGE_COST => 1,
137            SSTORE_COST => 2,
138            SLOAD_COST => 3,
139            INVALID_INSTRUCTION_COST => 4,
140            1..=4 => panic!("Reserved gas cost values overlap with actual gas costs"),
141            x => {
142                assert!(x <= u8::MAX as u32, "Gas cost doesn't fit into 8 bits");
143                x as u8
144            }
145        }
146    }
147
148    pub(crate) fn get_static_gas_cost(&self) -> u32 {
149        match self.static_gas_cost {
150            1 => L1_MESSAGE_COST,
151            2 => SSTORE_COST,
152            3 => SLOAD_COST,
153            4 => INVALID_INSTRUCTION_COST,
154            x => x.into(),
155        }
156    }
157
158    pub(crate) fn predicate(&self) -> Predicate {
159        unsafe { std::mem::transmute(self.predicate_and_mode_requirements >> 2) }
160    }
161
162    pub(crate) fn mode_requirements(&self) -> ModeRequirements {
163        ModeRequirements(self.predicate_and_mode_requirements & 0b11)
164    }
165
166    pub(crate) fn write_source(mut self, sw: &impl SourceWriter) -> Self {
167        sw.write_source(&mut self);
168        self
169    }
170
171    pub(crate) fn write_destination(mut self, sw: &impl DestinationWriter) -> Self {
172        sw.write_destination(&mut self);
173        self
174    }
175
176    /// Returns the register that receives the second output (`dst1`) of the instruction.
177    /// Stored for every opcode during decoding so `full_boilerplate` can clear it when unwritten.
178    pub(crate) fn dst1_register(&self) -> Register {
179        self.destination_registers.register2()
180    }
181}
182
183/// Register passed as a first instruction argument.
184///
185/// It must not be used simultaneously with [`AbsoluteStack`], [`RelativeStack`], [`AdvanceStackPointer`],
186/// or [`CodePage`].
187#[derive(Debug, Clone, Copy)]
188#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
189pub struct Register1(pub Register);
190
191/// Register passed as a second instruction argument.
192#[derive(Debug, Clone, Copy)]
193#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
194pub struct Register2(pub Register);
195
196impl Source for Register1 {
197    fn get_with_pointer_flag(args: &Arguments, state: &mut impl Addressable) -> (U256, bool) {
198        let register = args.source_registers.register1();
199        (register.value(state), register.pointer_flag(state))
200    }
201}
202
203impl SourceWriter for Register1 {
204    fn write_source(&self, args: &mut Arguments) {
205        args.source_registers.set_register1(self.0);
206    }
207}
208
209impl Source for Register2 {
210    fn get_with_pointer_flag(args: &Arguments, state: &mut impl Addressable) -> (U256, bool) {
211        let register = args.source_registers.register2();
212        (register.value(state), register.pointer_flag(state))
213    }
214}
215
216impl SourceWriter for Register2 {
217    fn write_source(&self, args: &mut Arguments) {
218        args.source_registers.set_register2(self.0);
219    }
220}
221
222impl Destination for Register1 {
223    fn set(args: &Arguments, state: &mut impl Addressable, value: U256) {
224        args.destination_registers.register1().set(state, value);
225    }
226
227    fn set_fat_ptr(args: &Arguments, state: &mut impl Addressable, value: U256) {
228        args.destination_registers.register1().set_ptr(state, value);
229    }
230}
231
232impl DestinationWriter for Register1 {
233    fn write_destination(&self, args: &mut Arguments) {
234        args.destination_registers.set_register1(self.0);
235    }
236}
237
238impl Destination for Register2 {
239    fn set(args: &Arguments, state: &mut impl Addressable, value: U256) {
240        args.destination_registers.register2().set(state, value);
241        state.mark_dst1_written();
242    }
243
244    fn set_fat_ptr(args: &Arguments, state: &mut impl Addressable, value: U256) {
245        args.destination_registers.register2().set_ptr(state, value);
246        state.mark_dst1_written();
247    }
248}
249
250impl DestinationWriter for Register2 {
251    fn write_destination(&self, args: &mut Arguments) {
252        args.destination_registers.set_register2(self.0);
253    }
254}
255
256/// Immediate value passed as a first instruction arg.
257#[derive(Debug, Clone, Copy)]
258#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
259pub struct Immediate1(pub u16);
260
261/// Immediate value passed as a second instruction arg.
262#[derive(Debug, Clone, Copy)]
263#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
264pub struct Immediate2(pub u16);
265
266impl Immediate1 {
267    pub(crate) fn get_u16(args: &Arguments) -> u16 {
268        args.immediate1
269    }
270}
271
272impl Immediate2 {
273    pub(crate) fn get_u16(args: &Arguments) -> u16 {
274        args.immediate2
275    }
276}
277
278impl Source for Immediate1 {
279    fn get(args: &Arguments, _state: &mut impl Addressable) -> U256 {
280        U256([args.immediate1.into(), 0, 0, 0])
281    }
282}
283
284impl SourceWriter for Immediate1 {
285    fn write_source(&self, args: &mut Arguments) {
286        args.immediate1 = self.0;
287    }
288}
289
290impl Source for Immediate2 {
291    fn get(args: &Arguments, _state: &mut impl Addressable) -> U256 {
292        U256([args.immediate2.into(), 0, 0, 0])
293    }
294}
295
296impl SourceWriter for Immediate2 {
297    fn write_source(&self, args: &mut Arguments) {
298        args.immediate2 = self.0;
299    }
300}
301
302/// Combination of a register and an immediate value wrapped by [`AbsoluteStack`], [`RelativeStack`],
303/// [`AdvanceStackPointer`] and [`CodePage`] addressing modes.
304#[derive(Debug, Clone, Copy)]
305#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
306pub struct RegisterAndImmediate {
307    /// Immediate value.
308    pub immediate: u16,
309    /// Register spec.
310    pub register: Register,
311}
312
313/// Any addressing mode that uses reg + imm in some way.
314/// They all encode their parameters in the same way.
315trait RegisterPlusImmediate {
316    fn inner(&self) -> &RegisterAndImmediate;
317}
318
319impl<T: RegisterPlusImmediate> SourceWriter for T {
320    fn write_source(&self, args: &mut Arguments) {
321        args.immediate1 = self.inner().immediate;
322        args.source_registers.set_register1(self.inner().register);
323    }
324}
325
326impl<T: RegisterPlusImmediate> DestinationWriter for T {
327    fn write_destination(&self, args: &mut Arguments) {
328        args.immediate2 = self.inner().immediate;
329        args.destination_registers
330            .set_register1(self.inner().register);
331    }
332}
333
334trait StackAddressing {
335    fn address_for_get(args: &Arguments, state: &mut impl Addressable) -> u16;
336    fn address_for_set(args: &Arguments, state: &mut impl Addressable) -> u16;
337}
338
339impl<T: StackAddressing> Source for T {
340    fn get_with_pointer_flag(args: &Arguments, state: &mut impl Addressable) -> (U256, bool) {
341        let address = Self::address_for_get(args, state);
342        (
343            state.read_stack(address),
344            state.read_stack_pointer_flag(address),
345        )
346    }
347}
348
349impl<T: StackAddressing> Destination for T {
350    fn set(args: &Arguments, state: &mut impl Addressable, value: U256) {
351        let address = Self::address_for_set(args, state);
352        state.write_stack(address, value);
353        state.clear_stack_pointer_flag(address);
354    }
355
356    fn set_fat_ptr(args: &Arguments, state: &mut impl Addressable, value: U256) {
357        let address = Self::address_for_set(args, state);
358        state.write_stack(address, value);
359        state.set_stack_pointer_flag(address);
360    }
361}
362
363fn source_stack_address(args: &Arguments, state: &mut impl Addressable) -> u16 {
364    compute_stack_address(state, args.source_registers.register1(), args.immediate1)
365}
366
367pub(crate) fn destination_stack_address(args: &Arguments, state: &mut impl Addressable) -> u16 {
368    compute_stack_address(
369        state,
370        args.destination_registers.register1(),
371        args.immediate2,
372    )
373}
374
375/// Computes register + immediate (mod 2^16).
376/// Stack addresses are always in that remainder class anyway.
377#[allow(clippy::cast_possible_truncation)]
378fn compute_stack_address(state: &mut impl Addressable, register: Register, immediate: u16) -> u16 {
379    (register.value(state).low_u32() as u16).wrapping_add(immediate)
380}
381
382/// Absolute addressing into stack.
383#[derive(Debug, Clone, Copy)]
384#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
385pub struct AbsoluteStack(pub RegisterAndImmediate);
386
387impl RegisterPlusImmediate for AbsoluteStack {
388    fn inner(&self) -> &RegisterAndImmediate {
389        &self.0
390    }
391}
392
393impl StackAddressing for AbsoluteStack {
394    fn address_for_get(args: &Arguments, state: &mut impl Addressable) -> u16 {
395        source_stack_address(args, state)
396    }
397
398    fn address_for_set(args: &Arguments, state: &mut impl Addressable) -> u16 {
399        destination_stack_address(args, state)
400    }
401}
402
403/// Relative addressing into stack (relative to the VM stack pointer).
404#[derive(Debug, Clone, Copy)]
405#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
406pub struct RelativeStack(pub RegisterAndImmediate);
407
408impl RegisterPlusImmediate for RelativeStack {
409    fn inner(&self) -> &RegisterAndImmediate {
410        &self.0
411    }
412}
413
414impl StackAddressing for RelativeStack {
415    fn address_for_get(args: &Arguments, state: &mut impl Addressable) -> u16 {
416        state
417            .stack_pointer()
418            .wrapping_sub(source_stack_address(args, state))
419    }
420
421    fn address_for_set(args: &Arguments, state: &mut impl Addressable) -> u16 {
422        state
423            .stack_pointer()
424            .wrapping_sub(destination_stack_address(args, state))
425    }
426}
427
428/// Same as [`RelativeStack`], but moves the stack pointer on access (decreases it when reading data;
429/// increases when writing data).
430#[derive(Debug, Clone, Copy)]
431#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
432pub struct AdvanceStackPointer(pub RegisterAndImmediate);
433
434impl RegisterPlusImmediate for AdvanceStackPointer {
435    fn inner(&self) -> &RegisterAndImmediate {
436        &self.0
437    }
438}
439
440impl StackAddressing for AdvanceStackPointer {
441    fn address_for_get(args: &Arguments, state: &mut impl Addressable) -> u16 {
442        let offset = source_stack_address(args, state);
443        let sp = state.stack_pointer();
444        *sp = sp.wrapping_sub(offset);
445        *sp
446    }
447
448    fn address_for_set(args: &Arguments, state: &mut impl Addressable) -> u16 {
449        let offset = destination_stack_address(args, state);
450        let sp = state.stack_pointer();
451        let address_to_set = *sp;
452        *sp = sp.wrapping_add(offset);
453        address_to_set
454    }
455}
456
457/// Absolute addressing into the code page of the currently executing program.
458#[derive(Debug, Clone, Copy)]
459#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
460pub struct CodePage(pub RegisterAndImmediate);
461
462impl RegisterPlusImmediate for CodePage {
463    fn inner(&self) -> &RegisterAndImmediate {
464        &self.0
465    }
466}
467
468impl Source for CodePage {
469    fn get(args: &Arguments, state: &mut impl Addressable) -> U256 {
470        let address = source_stack_address(args, state);
471        state
472            .code_page()
473            .get(address as usize)
474            .copied()
475            .unwrap_or(U256::zero())
476    }
477}
478
479/// Representation of one of 16 VM registers.
480#[derive(Debug, Clone, Copy)]
481pub struct Register(u8);
482
483impl Register {
484    /// Creates a register with the specified 0-based index.
485    ///
486    /// # Panics
487    ///
488    /// Panics if `n >= 16`; EraVM has 16 registers.
489    pub const fn new(n: u8) -> Self {
490        assert!(n < 16, "EraVM has 16 registers");
491        Self(n)
492    }
493
494    fn value(self, state: &mut impl Addressable) -> U256 {
495        unsafe { *state.registers().get_unchecked(self.0 as usize) }
496    }
497
498    fn pointer_flag(self, state: &mut impl Addressable) -> bool {
499        *state.register_pointer_flags() & (1 << self.0) != 0
500    }
501
502    fn set(self, state: &mut impl Addressable, value: U256) {
503        if self.0 != 0 {
504            unsafe { *state.registers().get_unchecked_mut(self.0 as usize) = value };
505            *state.register_pointer_flags() &= !(1 << self.0);
506        }
507    }
508
509    fn set_ptr(self, state: &mut impl Addressable, value: U256) {
510        if self.0 != 0 {
511            unsafe { *state.registers().get_unchecked_mut(self.0 as usize) = value };
512            *state.register_pointer_flags() |= 1 << self.0;
513        }
514    }
515
516    /// Zeroes the register and clears its pointer flag. Used to clear an unwritten `dst1`.
517    pub(crate) fn set_zero(self, state: &mut impl Addressable) {
518        self.set(state, U256::zero());
519    }
520}
521
522#[cfg(feature = "arbitrary")]
523impl<'a> Arbitrary<'a> for Register {
524    #[allow(clippy::cast_possible_truncation)] // false positive: the value is <16
525    fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self, arbitrary::Error> {
526        Ok(Register(u.choose_index(16)? as u8))
527    }
528}
529
530#[derive(Hash, Debug)]
531struct PackedRegisters(u8);
532
533impl PackedRegisters {
534    fn register1(&self) -> Register {
535        Register(self.0 >> 4)
536    }
537    fn set_register1(&mut self, value: Register) {
538        self.0 &= 0xf;
539        self.0 |= value.0 << 4;
540    }
541    fn register2(&self) -> Register {
542        Register(self.0 & 0xf)
543    }
544    fn set_register2(&mut self, value: Register) {
545        self.0 &= 0xf0;
546        self.0 |= value.0;
547    }
548}
549
550/// All supported addressing modes for the first source argument.
551#[enum_dispatch(SourceWriter)]
552#[derive(Debug, Clone, Copy)]
553#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
554pub enum AnySource {
555    /// Register mode.
556    Register1,
557    /// Immediate mode.
558    Immediate1,
559    /// Absolute stack addressing.
560    AbsoluteStack,
561    /// Relative stack addressing.
562    RelativeStack,
563    /// Relative stack addressing that updates the stack pointer on access.
564    AdvanceStackPointer,
565    /// Addressing into the code page of the executing contract.
566    CodePage,
567}
568
569/// Register or immediate addressing modes required by some VM instructions.
570#[enum_dispatch(SourceWriter)]
571#[derive(Debug, Clone, Copy)]
572#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
573pub enum RegisterOrImmediate {
574    /// Register mode.
575    Register1,
576    /// Immediate mode.
577    Immediate1,
578}
579
580/// Error converting [`AnySource`] to [`RegisterOrImmediate`].
581#[derive(Debug)]
582pub struct NotRegisterOrImmediate;
583
584impl TryFrom<AnySource> for RegisterOrImmediate {
585    type Error = NotRegisterOrImmediate;
586
587    fn try_from(value: AnySource) -> Result<Self, Self::Error> {
588        match value {
589            AnySource::Register1(r) => Ok(RegisterOrImmediate::Register1(r)),
590            AnySource::Immediate1(r) => Ok(RegisterOrImmediate::Immediate1(r)),
591            _ => Err(NotRegisterOrImmediate),
592        }
593    }
594}
595
596/// All supported addressing modes for the first destination argument.
597#[enum_dispatch(DestinationWriter)]
598#[derive(Debug, Clone, Copy)]
599#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
600pub enum AnyDestination {
601    /// Register mode.
602    Register1,
603    /// Absolute stack addressing.
604    AbsoluteStack,
605    /// Relative stack addressing.
606    RelativeStack,
607    /// Relative stack addressing that updates the stack pointer on access.
608    AdvanceStackPointer,
609}