wasm/execution/runtime_structure/
value_stack.rs

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