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