wasm/execution/
value_stack.rs

1use core::mem::MaybeUninit;
2
3use alloc::vec::Vec;
4
5use crate::addrs::FuncAddr;
6use crate::config::Config;
7use crate::core::fixed_capacity_vec::FixedCapacityVec;
8use crate::core::indices::LocalIdx;
9use crate::core::reader::types::{FuncType, ValType};
10use crate::core::utils::ToUsizeExt;
11use crate::execution::assert_validated::UnwrapValidatedExt;
12use crate::execution::value::Value;
13use crate::RuntimeError;
14
15/// The stack at runtime containing
16/// 1. Values
17/// 2. Labels
18/// 3. Activations
19///
20/// See <https://webassembly.github.io/spec/core/exec/runtime.html#stack>
21#[derive(Debug, Clone)]
22pub(crate) struct Stack {
23    /// WASM values on the stack, i.e. the actual data that instructions operate on
24    values: FixedCapacityVec<Value>,
25
26    /// Call frames
27    ///
28    /// Each time a function is called, a new frame is pushed, whenever a function returns, a frame is popped
29    frames: FixedCapacityVec<CallFrame>,
30}
31
32impl Stack {
33    pub fn new<T: Config>(
34        params_to_base_call_frame: Vec<Value>,
35        base_call_frame_func_ty: &FuncType,
36        base_call_frame_remaining_locals: &[ValType],
37    ) -> Result<Self, RuntimeError> {
38        let mut stack = Self {
39            values: FixedCapacityVec::with_capacity(T::MAX_VALUE_STACK_SIZE),
40            frames: FixedCapacityVec::with_capacity(T::MAX_CALL_STACK_SIZE),
41        };
42
43        stack.values.push_from_slice(&params_to_base_call_frame)?;
44
45        debug_assert!(
46            stack.values.len() >= base_call_frame_func_ty.params.valtypes.len(),
47            "when pushing a new call frame, at least as many values need to be on the stack as required by the new call frames's function"
48        );
49
50        // the topmost `param_count` values are transferred into/consumed by this new call frame
51        let param_count = base_call_frame_func_ty.params.valtypes.len();
52        let call_frame_base_idx = stack.values.len() - param_count;
53
54        // verify that enough space is on the stack to push the remaining locals
55        if base_call_frame_remaining_locals.len() + stack.values.len() >= stack.values.capacity() {
56            return Err(RuntimeError::StackExhaustion);
57        }
58
59        // after the params, put the additional locals
60        for local in base_call_frame_remaining_locals {
61            // SAFETY: the previous if checks that sufficient space is one the stack
62            unsafe { stack.push_value_unchecked(Value::default_from_ty(*local)) };
63        }
64
65        // now that the locals are all populated, the actual stack section of this call frame begins
66        let value_stack_base_idx = stack.values.len();
67
68        stack.frames.push(CallFrame {
69            return_func_addr: MaybeUninit::uninit(),
70            return_addr: MaybeUninit::uninit(),
71            value_stack_base_idx,
72            call_frame_base_idx,
73            return_value_count: base_call_frame_func_ty.returns.valtypes.len(),
74            return_stp: MaybeUninit::uninit(),
75        })?;
76
77        Ok(stack)
78    }
79
80    pub(super) fn into_values(mut self) -> Vec<Value> {
81        self.values
82            .pop_into_slice(self.values.len())
83            .unwrap_validated()
84            .to_vec()
85    }
86
87    /// Pop a value from the value stack
88    #[inline(always)]
89    pub fn pop_value(&mut self) -> Value {
90        // If there is at least one call frame, we shall not pop values past the current
91        // call frame. However, there is one legitimate reason to pop when there is **no** current
92        // call frame: after the outermost function returns, to extract the final return values of
93        // this interpreter invocation.
94        debug_assert!(
95            if !self.frames.is_empty() {
96                self.values.len() > self.current_call_frame().value_stack_base_idx
97            } else {
98                true
99            },
100            "can not pop values past the current call frame"
101        );
102
103        debug_assert!(!self.values.is_empty(), "pop results in stack underflow");
104
105        // SAFETY: this is safe only if there is at least one `Value` on the stack, which must
106        // belong to the current `CallFrame`.
107        unsafe { self.pop_value_unchecked() }
108    }
109
110    /// Pop a [`Value`] from the value stack
111    ///
112    /// # Safety
113    ///
114    /// This will underflow the stack and cause undefined behavior, if the stack is empty. To
115    /// check, before pushing, assure that [`Self::values`] is not empty.
116    #[inline(always)]
117    unsafe fn pop_value_unchecked(&mut self) -> Value {
118        // SAFETY: safe only if stack contains at least one element
119        unsafe { self.values.pop_unchecked() }
120    }
121
122    /// Returns a cloned copy of the top value on the stack, or `None` if the stack is empty
123    pub fn peek_value(&self) -> Option<Value> {
124        self.values.peek().ok().cloned()
125    }
126
127    /// Push a value to the value stack after veryfing that this will not overflow the stack
128    pub fn push_value(&mut self, value: Value) -> Result<(), RuntimeError> {
129        // check for value stack exhaustion
130        if self.values.len() >= self.values.capacity() {
131            return Err(RuntimeError::StackExhaustion);
132        }
133
134        // SAFETY: The prior if statement verifies that the stack is not yet exhausted
135        unsafe { self.push_value_unchecked(value) };
136
137        Ok(())
138    }
139
140    /// Push a [`Value`] to the value stack
141    ///
142    /// # Safety
143    ///
144    /// This will overflow the stack and cause undefined behavior, if the stack is already full. To
145    /// check, before pushing, assure that the [`Self::values`] is not full.
146    #[inline(always)]
147    unsafe fn push_value_unchecked(&mut self, value: Value) {
148        // SAFETY: safe when called with remaining space on the stack.
149        unsafe { self.values.push_unchecked(value) }
150    }
151
152    /// Returns a shared reference to a specific local by its index in the current call frame.
153    pub fn get_local(&self, idx: LocalIdx) -> &Value {
154        let idx = idx.into_inner().into_usize();
155        let call_frame_base_idx = self.current_call_frame().call_frame_base_idx;
156        self.values
157            .get(call_frame_base_idx + idx)
158            .unwrap_validated()
159    }
160
161    /// Returns a mutable reference to a specific local by its index in the current call frame.
162    pub fn get_local_mut(&mut self, idx: LocalIdx) -> &mut Value {
163        let idx = idx.into_inner().into_usize();
164        let call_frame_base_idx = self.current_call_frame().call_frame_base_idx;
165        self.values
166            .get_mut(call_frame_base_idx + idx)
167            .unwrap_validated()
168    }
169
170    /// Get a shared reference to the current [`CallFrame`]
171    ///
172    /// # Safety
173    ///
174    /// This will underflow if no active call frame is on the stack.
175    pub fn current_call_frame(&self) -> &CallFrame {
176        // SAFETY: must only be called if there is at least one callframe on the stack.
177        unsafe { self.frames.peek_unchecked() }
178    }
179
180    /// Pop a [`CallFrame`] from the call stack, returning the caller function store address, return address, and the return stp
181    ///
182    /// Returns `None` if the base call frame was popped. Its information cannot
183    /// be retrieved.
184    pub fn pop_call_frame(&mut self) -> Option<(FuncAddr, usize, usize)> {
185        let CallFrame {
186            return_func_addr,
187            return_addr,
188            call_frame_base_idx,
189            return_value_count,
190            return_stp,
191            ..
192        } = self.frames.pop().unwrap_validated();
193
194        let remove_count = self.values.len() - call_frame_base_idx - return_value_count;
195
196        self.remove_in_between(remove_count, return_value_count);
197
198        debug_assert_eq!(
199            self.values.len(),
200            call_frame_base_idx + return_value_count,
201            "after a function call finished, the stack must have exactly as many values as it had before calling the function plus the number of function return values"
202        );
203
204        // If this was the base call frame, do not return its uninitialized fields
205        if self.call_frame_count() == 0 {
206            None
207        } else {
208            // SAFETY: This is sound, because we just checked that the call
209            // frame which we just popped is not the base call frame and only
210            // the base call frame can contain uninitialized fields.
211            unsafe {
212                Some((
213                    return_func_addr.assume_init(),
214                    return_addr.assume_init(),
215                    return_stp.assume_init(),
216                ))
217            }
218        }
219    }
220
221    /// Push a call frame to the call stack
222    ///
223    /// Takes the current [`Self::values`]'s length as [`CallFrame::value_stack_base_idx`].
224    pub fn push_call_frame<C: Config>(
225        &mut self,
226        return_func_addr: FuncAddr,
227        func_ty: &FuncType,
228        remaining_locals: &[ValType],
229        return_addr: usize,
230        return_stp: usize,
231    ) -> Result<(), RuntimeError> {
232        // check for call stack exhaustion
233        if self.call_frame_count() > C::MAX_CALL_STACK_SIZE {
234            return Err(RuntimeError::StackExhaustion);
235        }
236
237        debug_assert!(
238            self.values.len()>= func_ty.params.valtypes.len(),
239            "when pushing a new call frame, at least as many values need to be on the stack as required by the new call frames's function"
240        );
241
242        // the topmost `param_count` values are transferred into/consumed by this new call frame
243        let param_count = func_ty.params.valtypes.len();
244        let call_frame_base_idx = self.values.len() - param_count;
245
246        // verify that enough space is on the stack to push the remaining locals
247        if remaining_locals.len() + self.values.len() >= self.values.capacity() {
248            return Err(RuntimeError::StackExhaustion);
249        }
250
251        // after the params, put the additional locals
252        for local in remaining_locals {
253            // SAFETY: the previous if checks that sufficient space is one the stack
254            unsafe { self.push_value_unchecked(Value::default_from_ty(*local)) };
255        }
256
257        // now that the locals are all populated, the actual stack section of this call frame begins
258        let value_stack_base_idx = self.values.len();
259
260        self.frames.push(CallFrame {
261            return_func_addr: MaybeUninit::new(return_func_addr),
262            return_addr: MaybeUninit::new(return_addr),
263            value_stack_base_idx,
264            call_frame_base_idx,
265            return_value_count: func_ty.returns.valtypes.len(),
266            return_stp: MaybeUninit::new(return_stp),
267        })?;
268
269        Ok(())
270    }
271
272    /// Returns how many call frames are on the stack, in total.
273    pub fn call_frame_count(&self) -> usize {
274        self.frames.len()
275    }
276
277    /// Pop `n` elements from the value stack's tail as an iterator, with the first element being
278    /// closest to the **bottom** of the value stack
279    ///
280    /// Note that this is providing the values in reverse order compared to popping `n` values
281    /// (which would yield the element closest to the **top** of the value stack first).
282    pub fn pop_tail_iter(&mut self, n: usize) -> Vec<Value> {
283        let tail = self.values.pop_into_slice(n).unwrap_validated().to_vec();
284        debug_assert_eq!(tail.len(), n);
285        tail
286    }
287
288    /// Remove `remove_count` values from the stack, keeping the topmost `keep_count` values
289    ///
290    /// From the stack, remove `remove_count` elements, by sliding down the `keep_count` topmost
291    /// values `remove_count` positions.
292    ///
293    /// **Effects**
294    ///
295    /// - after the operation, [`Stack`] will contain `remove_count` fewer elements
296    /// - `keep_count` topmost elements will be identical before and after the operation
297    /// - all elements below the `remove_count + keep_count` topmost stack entry remain
298    pub fn remove_in_between(&mut self, remove_count: usize, keep_count: usize) {
299        self.values.remove_in_between(remove_count, keep_count);
300    }
301}
302
303/// The [WASM spec](https://webassembly.github.io/spec/core/exec/runtime.html#stack) calls this `Activations`, however it refers to the call frames of functions.
304#[derive(Debug, Clone, Copy)]
305pub(crate) struct CallFrame {
306    /// Store address of the function that called this [`CallFrame`]'s function
307    ///
308    /// This is always uninitialized for the base call frame and initialized for
309    /// all other call frames.
310    pub return_func_addr: MaybeUninit<FuncAddr>,
311
312    /// Value that the PC has to be set to when this function returns
313    ///
314    /// This is always uninitialized for the base call frame and initialized for
315    /// all other call frames.
316    pub return_addr: MaybeUninit<usize>,
317
318    /// The index to the lowermost value in [`Stack::values`] belonging to this [`CallFrame`]'s
319    /// stack
320    ///
321    /// Values below this may still belong to this [`CallFrame`], but they are locals. Consequently,
322    /// this is the lowest index down to which the stack may be popped in this [`CallFrame`].
323    /// However, clearing up this [`CallFrame`] may require further popping, down to (and
324    /// including!) the index stored in [`Self::call_frame_base_idx`].
325    pub value_stack_base_idx: usize,
326
327    /// The index to the lowermost value on [`Stack::values`] that belongs to this [`CallFrame`]
328    ///
329    /// Clearing this [`CallFrame`] requires popping all elements on [`Stack::values`] down to (and
330    /// including!) this index.
331    pub call_frame_base_idx: usize,
332
333    /// Number of return values to retain on [`Stack::values`] when unwinding/popping a [`CallFrame`]
334    pub return_value_count: usize,
335
336    /// Value that the stp has to be set to when this function returns
337    ///
338    /// This is always uninitialized for the base call frame and initialized for
339    /// all other call frames.
340    pub return_stp: MaybeUninit<usize>,
341}