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