gwasmi/engine/stack/
mod.rs

1mod frames;
2mod values;
3
4pub use self::{
5    frames::{CallStack, FuncFrame},
6    values::{ValueStack, ValueStackPtr},
7};
8use crate::{
9    core::UntypedValue,
10    engine::{code_map::CodeMap, func_types::FuncTypeRegistry, FuncParams},
11    func::{HostFuncEntity, WasmFuncEntity},
12    AsContext,
13    Instance,
14    StoreContextMut,
15};
16use core::{
17    fmt::{self, Display},
18    mem::size_of,
19};
20use wasmi_core::{Trap, TrapCode};
21
22/// Default value for initial value stack height in bytes.
23const DEFAULT_MIN_VALUE_STACK_HEIGHT: usize = 1024;
24
25/// Default value for maximum value stack height in bytes.
26const DEFAULT_MAX_VALUE_STACK_HEIGHT: usize = 1024 * DEFAULT_MIN_VALUE_STACK_HEIGHT;
27
28/// Default value for maximum recursion depth.
29const DEFAULT_MAX_RECURSION_DEPTH: usize = 1024;
30
31/// Returns a [`TrapCode`] signalling a stack overflow.
32#[cold]
33fn err_stack_overflow() -> TrapCode {
34    TrapCode::StackOverflow
35}
36
37/// The configured limits of the Wasm stack.
38#[derive(Debug, Copy, Clone)]
39pub struct StackLimits {
40    /// The initial value stack height that the Wasm stack prepares.
41    initial_value_stack_height: usize,
42    /// The maximum value stack height in use that the Wasm stack allows.
43    maximum_value_stack_height: usize,
44    /// The maximum number of nested calls that the Wasm stack allows.
45    maximum_recursion_depth: usize,
46}
47
48/// An error that may occur when configuring [`StackLimits`].
49#[derive(Debug)]
50pub enum LimitsError {
51    /// The initial value stack height exceeds the maximum value stack height.
52    InitialValueStackExceedsMaximum,
53}
54
55impl Display for LimitsError {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        match self {
58            LimitsError::InitialValueStackExceedsMaximum => {
59                write!(f, "initial value stack height exceeds maximum stack height")
60            }
61        }
62    }
63}
64
65impl StackLimits {
66    /// Creates a new [`StackLimits`] configuration.
67    ///
68    /// # Errors
69    ///
70    /// If the `initial_value_stack_height` exceeds `maximum_value_stack_height`.
71    pub fn new(
72        initial_value_stack_height: usize,
73        maximum_value_stack_height: usize,
74        maximum_recursion_depth: usize,
75    ) -> Result<Self, LimitsError> {
76        if initial_value_stack_height > maximum_value_stack_height {
77            return Err(LimitsError::InitialValueStackExceedsMaximum);
78        }
79        Ok(Self {
80            initial_value_stack_height,
81            maximum_value_stack_height,
82            maximum_recursion_depth,
83        })
84    }
85}
86
87impl Default for StackLimits {
88    fn default() -> Self {
89        let register_len = size_of::<UntypedValue>();
90        let initial_value_stack_height = DEFAULT_MIN_VALUE_STACK_HEIGHT / register_len;
91        let maximum_value_stack_height = DEFAULT_MAX_VALUE_STACK_HEIGHT / register_len;
92        Self {
93            initial_value_stack_height,
94            maximum_value_stack_height,
95            maximum_recursion_depth: DEFAULT_MAX_RECURSION_DEPTH,
96        }
97    }
98}
99
100/// Data structure that combines both value stack and call stack.
101#[derive(Debug, Default)]
102pub struct Stack {
103    /// The value stack.
104    pub values: ValueStack,
105    /// The frame stack.
106    pub frames: CallStack,
107}
108
109impl Stack {
110    /// Creates a new [`Stack`] given the [`Config`].
111    ///
112    /// [`Config`]: [`crate::Config`]
113    pub fn new(limits: StackLimits) -> Self {
114        let frames = CallStack::new(limits.maximum_recursion_depth);
115        let values = ValueStack::new(
116            limits.initial_value_stack_height,
117            limits.maximum_value_stack_height,
118        );
119        Self { values, frames }
120    }
121
122    /// Create an empty [`Stack`].
123    ///
124    /// # Note
125    ///
126    /// Empty stacks require no heap allocations and are cheap to construct.
127    pub fn empty() -> Self {
128        Self {
129            values: ValueStack::empty(),
130            frames: CallStack::default(),
131        }
132    }
133
134    /// Returns `true` if the [`Stack`] is empty.
135    ///
136    /// # Note
137    ///
138    /// Empty [`Stack`] instances are usually non-usable dummy instances.
139    pub fn is_empty(&self) -> bool {
140        self.values.is_empty()
141    }
142
143    /// Prepares the [`Stack`] for a call to the Wasm function.
144    pub fn prepare_wasm_call(
145        &mut self,
146        wasm_func: &WasmFuncEntity,
147        code_map: &CodeMap,
148    ) -> Result<(), TrapCode> {
149        let header = code_map.header(wasm_func.func_body());
150        self.values.prepare_wasm_call(header)?;
151        let ip = code_map.instr_ptr(header.iref());
152        let instance = wasm_func.instance();
153        self.frames.init(ip, instance);
154        Ok(())
155    }
156
157    /// Executes the given host function as root.
158    pub fn call_host_as_root<T>(
159        &mut self,
160        ctx: StoreContextMut<T>,
161        host_func: HostFuncEntity,
162        func_types: &FuncTypeRegistry,
163    ) -> Result<(), Trap> {
164        self.call_host_impl(ctx, host_func, None, func_types)
165    }
166
167    /// Executes the given host function.
168    ///
169    /// # Errors
170    ///
171    /// - If the host function returns a host side error or trap.
172    /// - If the value stack overflowed upon pushing parameters or results.
173    #[inline(always)]
174    pub fn call_host_impl<T>(
175        &mut self,
176        ctx: StoreContextMut<T>,
177        host_func: HostFuncEntity,
178        instance: Option<&Instance>,
179        func_types: &FuncTypeRegistry,
180    ) -> Result<(), Trap> {
181        // The host function signature is required for properly
182        // adjusting, inspecting and manipulating the value stack.
183        let (input_types, output_types) = func_types
184            .resolve_func_type(host_func.ty_dedup())
185            .params_results();
186        // In case the host function returns more values than it takes
187        // we are required to extend the value stack.
188        let len_inputs = input_types.len();
189        let len_outputs = output_types.len();
190        let max_inout = len_inputs.max(len_outputs);
191        self.values.reserve(max_inout)?;
192        let delta = if len_outputs > len_inputs {
193            // Note: We have to save the delta of values pushed
194            //       so that we can drop them in case the host
195            //       function fails to execute properly.
196            let delta = len_outputs - len_inputs;
197            self.values.extend_zeros(delta);
198            delta
199        } else {
200            0
201        };
202        let params_results = FuncParams::new(
203            self.values.peek_as_slice_mut(max_inout),
204            len_inputs,
205            len_outputs,
206        );
207        // Now we are ready to perform the host function call.
208        // Note: We need to clone the host function due to some borrowing issues.
209        //       This should not be a big deal since host functions usually are cheap to clone.
210        let trampoline = ctx
211            .as_context()
212            .store
213            .resolve_trampoline(host_func.trampoline())
214            .clone();
215        trampoline
216            .call(ctx, instance, params_results)
217            .map_err(|error| {
218                // Note: We drop the values that have been temporarily added to
219                //       the stack to act as parameter and result buffer for the
220                //       called host function. Since the host function failed we
221                //       need to clean up the temporary buffer values here.
222                //       This is required for resumable calls to work properly.
223                self.values.drop(delta);
224                error
225            })?;
226        // If the host functions returns fewer results than it receives parameters
227        // the value stack needs to be shrinked for the delta.
228        if len_outputs < len_inputs {
229            let delta = len_inputs - len_outputs;
230            self.values.drop(delta);
231        }
232        // At this point the host function has been called and has directly
233        // written its results into the value stack so that the last entries
234        // in the value stack are the result values of the host function call.
235        Ok(())
236    }
237
238    /// Clears both value and call stacks.
239    pub fn reset(&mut self) {
240        self.values.reset();
241        self.frames.reset();
242    }
243}