wasm/execution/
value_stack.rs

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