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}