wasm/execution/
value_stack.rs

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