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