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