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