1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//! WASM local slot.

use crate::{
    wasm::{ToLSBytes, Type},
    Error, Result,
};
use smallvec::SmallVec;
use wasmparser::ValType;

/// The type of a local slot.
#[derive(Debug, PartialEq, Eq)]
pub enum LocalSlotType {
    /// A function parameter.
    Parameter,
    /// A local variable.
    Variable,
}

/// A local slot.
///
/// Represents the type, location and addressing mode of a local
/// in the stack's local and argument area.
#[derive(Debug)]
pub struct LocalSlot {
    /// The type contained by this local slot.
    inner: ValType,
    /// The type of this local slot.
    ty: LocalSlotType,

    /// Stack pointer of the local slot.
    pub sp: usize,
}

impl LocalSlot {
    /// Create a new local slot.
    pub fn new(inner: ValType, ty: LocalSlotType, sp: usize) -> Self {
        Self { inner, ty, sp }
    }

    /// Get the type of this local slot.
    pub fn ty(&self) -> &LocalSlotType {
        &self.ty
    }

    /// Get the value type of this local slot.
    pub fn val_ty(&self) -> &ValType {
        &self.inner
    }
}

impl Type for LocalSlot {
    fn size(&self) -> usize {
        self.inner.size()
    }
}

/// Solidity's implementation uses 16 slots for locals.
/// ref: <https://docs.soliditylang.org/en/v0.8.20/internals/optimizer.html#stackcompressor>
#[derive(Default, Debug)]
pub struct Locals {
    inner: SmallVec<[LocalSlot; 16]>,
}

impl Locals {
    /// Get local from index.
    pub fn get(&self, index: usize) -> Result<&LocalSlot> {
        self.inner
            .get(index)
            .ok_or_else(|| Error::InvalidLocalIndex(index))
    }

    /// Get mutate local from index.
    pub fn get_mut(&mut self, index: usize) -> Result<&mut LocalSlot> {
        self.inner
            .get_mut(index)
            .ok_or_else(|| Error::InvalidLocalIndex(index))
    }

    /// Get the lower significant bytes of the byte offset of a local.
    ///
    /// - **Parameter**: If the local is a parameter, the offset is relative to the offset
    ///   of the calldata.
    /// - **Variable**: If the local is a variable, the offset is relative to the offset
    ///   of the memory.
    pub fn offset_of(&self, index: usize) -> Result<SmallVec<[u8; 32]>> {
        let local = self.get(index)?;
        let offset = if local.ty() == &LocalSlotType::Parameter {
            self.inner[..index].iter().fold(0, |acc, x| acc + x.align())
        } else {
            self.inner[..index]
                .iter()
                .filter(|x| x.ty() == &LocalSlotType::Variable)
                .fold(0, |acc, x| acc + x.align())
        }
        .to_ls_bytes()
        .to_vec()
        .into();

        Ok(offset)
    }

    /// Push a local slot.
    pub fn push(&mut self, slot: impl Into<LocalSlot>) {
        self.inner.push(slot.into())
    }

    /// Get the length of locals
    pub fn len(&self) -> usize {
        self.inner.len()
    }

    /// If the locals are empty.
    pub fn is_empty(&self) -> bool {
        self.inner.is_empty()
    }
}