wasm/execution/
value_stack.rs

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