radix_wasmi/engine/stack/
mod.rs

1mod frames;
2mod values;
3
4pub use self::{
5    frames::{CallStack, FuncFrame},
6    values::{ValueStack, ValueStackRef},
7};
8use super::{
9    code_map::{CodeMap, InstructionPtr},
10    func_types::FuncTypeRegistry,
11    FuncParams,
12};
13use crate::{
14    core::UntypedValue,
15    func::{HostFuncEntity, WasmFuncEntity},
16    AsContext,
17    AsContextMut,
18    Instance,
19};
20use core::{
21    fmt::{self, Display},
22    mem::size_of,
23};
24use wasmi_core::{Trap, TrapCode};
25
26/// Default value for initial value stack height in bytes.
27const DEFAULT_MIN_VALUE_STACK_HEIGHT: usize = 1024;
28
29/// Default value for maximum value stack height in bytes.
30const DEFAULT_MAX_VALUE_STACK_HEIGHT: usize = 1024 * DEFAULT_MIN_VALUE_STACK_HEIGHT;
31
32/// Default value for maximum recursion depth.
33const DEFAULT_MAX_RECURSION_DEPTH: usize = 1024;
34
35/// Returns a [`TrapCode`] signalling a stack overflow.
36#[cold]
37fn err_stack_overflow() -> TrapCode {
38    TrapCode::StackOverflow
39}
40
41/// The configured limits of the Wasm stack.
42#[derive(Debug, Copy, Clone)]
43pub struct StackLimits {
44    /// The initial value stack height that the Wasm stack prepares.
45    initial_value_stack_height: usize,
46    /// The maximum value stack height in use that the Wasm stack allows.
47    maximum_value_stack_height: usize,
48    /// The maximum number of nested calls that the Wasm stack allows.
49    maximum_recursion_depth: usize,
50}
51
52/// An error that may occur when configuring [`StackLimits`].
53#[derive(Debug)]
54pub enum LimitsError {
55    /// The initial value stack height exceeds the maximum value stack height.
56    InitialValueStackExceedsMaximum,
57}
58
59impl Display for LimitsError {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        match self {
62            LimitsError::InitialValueStackExceedsMaximum => {
63                write!(f, "initial value stack heihgt exceeds maximum stack height")
64            }
65        }
66    }
67}
68
69impl StackLimits {
70    /// Creates a new [`StackLimits`] configuration.
71    ///
72    /// # Errors
73    ///
74    /// If the `initial_value_stack_height` exceeds `maximum_value_stack_height`.
75    pub fn new(
76        initial_value_stack_height: usize,
77        maximum_value_stack_height: usize,
78        maximum_recursion_depth: usize,
79    ) -> Result<Self, LimitsError> {
80        if initial_value_stack_height > maximum_value_stack_height {
81            return Err(LimitsError::InitialValueStackExceedsMaximum);
82        }
83        Ok(Self {
84            initial_value_stack_height,
85            maximum_value_stack_height,
86            maximum_recursion_depth,
87        })
88    }
89}
90
91impl Default for StackLimits {
92    fn default() -> Self {
93        let register_len = size_of::<UntypedValue>();
94        let initial_value_stack_height = DEFAULT_MIN_VALUE_STACK_HEIGHT / register_len;
95        let maximum_value_stack_height = DEFAULT_MAX_VALUE_STACK_HEIGHT / register_len;
96        Self {
97            initial_value_stack_height,
98            maximum_value_stack_height,
99            maximum_recursion_depth: DEFAULT_MAX_RECURSION_DEPTH,
100        }
101    }
102}
103
104/// Data structure that combines both value stack and call stack.
105#[derive(Debug, Default)]
106pub struct Stack {
107    /// The value stack.
108    pub(crate) values: ValueStack,
109    /// The frame stack.
110    frames: CallStack,
111}
112
113impl Stack {
114    /// Creates a new [`Stack`] given the [`Config`].
115    ///
116    /// [`Config`]: [`crate::Config`]
117    pub fn new(limits: StackLimits) -> Self {
118        let frames = CallStack::new(limits.maximum_recursion_depth);
119        let values = ValueStack::new(
120            limits.initial_value_stack_height,
121            limits.maximum_value_stack_height,
122        );
123        Self { values, frames }
124    }
125
126    /// Create an empty [`Stack`].
127    ///
128    /// # Note
129    ///
130    /// Empty stacks require no heap allocations and are cheap to construct.
131    pub(crate) fn empty() -> Self {
132        Self {
133            values: ValueStack::empty(),
134            frames: CallStack::default(),
135        }
136    }
137
138    /// Returns `true` if the [`Stack`] is empty.
139    ///
140    /// # Note
141    ///
142    /// Empty [`Stack`] instances are usually non-usable dummy instances.
143    pub(crate) fn is_empty(&self) -> bool {
144        self.values.is_empty()
145    }
146
147    /// Push the given `frame` onto the call stack.
148    ///
149    /// # Note
150    ///
151    /// This API is required for resumable function calls so that the currently
152    /// active function frame can be pushed back onto the stack before returning
153    /// in a resumable state. Upon resumption the function frame can be popped
154    /// from the stack again.
155    pub(super) fn push_frame(&mut self, frame: FuncFrame) -> Result<(), TrapCode> {
156        self.frames.push(frame)
157    }
158
159    /// Pops the top most [`FuncFrame`] if any.
160    ///
161    /// # Note
162    ///
163    /// This is the counterpart method oo [`push_frame`] and also
164    /// required when resuming a resumable function execution to
165    /// pop of the Wasm function that was called just before the
166    /// errorneous host function invocation that caused the resumable
167    /// function invocation to pause.
168    ///
169    /// [`push_frame`]: [`Stack::push_frame`]
170    pub(super) fn pop_frame(&mut self) -> Option<FuncFrame> {
171        self.frames.pop()
172    }
173
174    /// Initializes the [`Stack`] for the given Wasm root function call.
175    pub(crate) fn call_wasm_root(
176        &mut self,
177        wasm_func: &WasmFuncEntity,
178        code_map: &CodeMap,
179    ) -> Result<FuncFrame, TrapCode> {
180        let iref = self.call_wasm_impl(wasm_func, code_map)?;
181        let instance = wasm_func.instance();
182        Ok(self.frames.init(iref, instance))
183    }
184
185    /// Prepares the [`Stack`] for the given Wasm function call.
186    pub(crate) fn call_wasm(
187        &mut self,
188        caller: &FuncFrame,
189        wasm_func: &WasmFuncEntity,
190        code_map: &CodeMap,
191    ) -> Result<FuncFrame, TrapCode> {
192        let ip = self.call_wasm_impl(wasm_func, code_map)?;
193        self.frames.push(*caller)?;
194        let instance = wasm_func.instance();
195        let frame = FuncFrame::new(ip, instance);
196        Ok(frame)
197    }
198
199    /// Prepares the [`Stack`] for execution of the given Wasm [`FuncFrame`].
200    pub(crate) fn call_wasm_impl(
201        &mut self,
202        wasm_func: &WasmFuncEntity,
203        code_map: &CodeMap,
204    ) -> Result<InstructionPtr, TrapCode> {
205        let header = code_map.header(wasm_func.func_body());
206        let max_stack_height = header.max_stack_height();
207        self.values.reserve(max_stack_height)?;
208        let len_locals = header.len_locals();
209        self.values
210            .extend_zeros(len_locals)
211            .expect("stack overflow is unexpected due to previous stack reserve");
212        let iref = header.iref();
213        let ip = code_map.instr_ptr(iref);
214        Ok(ip)
215    }
216
217    /// Signals the [`Stack`] to return the last Wasm function call.
218    ///
219    /// Returns the next function on the call stack if any.
220    pub fn return_wasm(&mut self) -> Option<FuncFrame> {
221        self.frames.pop()
222    }
223
224    /// Executes the given host function as root.
225    pub(crate) fn call_host_root<C>(
226        &mut self,
227        ctx: C,
228        host_func: HostFuncEntity<<C as AsContext>::UserState>,
229        func_types: &FuncTypeRegistry,
230    ) -> Result<(), Trap>
231    where
232        C: AsContextMut,
233    {
234        self.call_host_impl(ctx, host_func, None, func_types)
235    }
236
237    /// Executes the given host function called by a Wasm function.
238    pub(crate) fn call_host<C>(
239        &mut self,
240        ctx: C,
241        caller: &FuncFrame,
242        host_func: HostFuncEntity<<C as AsContext>::UserState>,
243        func_types: &FuncTypeRegistry,
244    ) -> Result<(), Trap>
245    where
246        C: AsContextMut,
247    {
248        let instance = caller.instance();
249        self.call_host_impl(ctx, host_func, Some(instance), func_types)
250    }
251
252    /// Executes the given host function.
253    ///
254    /// # Errors
255    ///
256    /// - If the host function returns a host side error or trap.
257    /// - If the value stack overflowed upon pushing parameters or results.
258    #[inline(never)]
259    fn call_host_impl<C>(
260        &mut self,
261        mut ctx: C,
262        host_func: HostFuncEntity<<C as AsContext>::UserState>,
263        instance: Option<Instance>,
264        func_types: &FuncTypeRegistry,
265    ) -> Result<(), Trap>
266    where
267        C: AsContextMut,
268    {
269        // The host function signature is required for properly
270        // adjusting, inspecting and manipulating the value stack.
271        let (input_types, output_types) = func_types
272            .resolve_func_type(host_func.ty_dedup())
273            .params_results();
274        // In case the host function returns more values than it takes
275        // we are required to extend the value stack.
276        let len_inputs = input_types.len();
277        let len_outputs = output_types.len();
278        let max_inout = len_inputs.max(len_outputs);
279        self.values.reserve(max_inout)?;
280        if len_outputs > len_inputs {
281            let delta = len_outputs - len_inputs;
282            self.values.extend_zeros(delta)?;
283        }
284        let params_results = FuncParams::new(
285            self.values.peek_as_slice_mut(max_inout),
286            len_inputs,
287            len_outputs,
288        );
289        // Now we are ready to perform the host function call.
290        // Note: We need to clone the host function due to some borrowing issues.
291        //       This should not be a big deal since host functions usually are cheap to clone.
292        host_func.call(&mut ctx, instance, params_results)?;
293        // If the host functions returns fewer results than it receives parameters
294        // the value stack needs to be shrinked for the delta.
295        if len_outputs < len_inputs {
296            let delta = len_inputs - len_outputs;
297            self.values.drop(delta);
298        }
299        // At this point the host function has been called and has directly
300        // written its results into the value stack so that the last entries
301        // in the value stack are the result values of the host function call.
302        Ok(())
303    }
304
305    /// Clears both value and call stacks.
306    pub fn clear(&mut self) {
307        self.values.clear();
308        self.frames.clear();
309    }
310}