radix_wasmi/engine/
mod.rs

1//! The `wasmi` interpreter.
2extern crate radix_wasmi_arena as wasmi_arena;
3
4pub mod bytecode;
5mod cache;
6pub mod code_map;
7mod config;
8pub mod executor;
9mod func_args;
10mod func_builder;
11mod func_types;
12mod resumable;
13pub mod stack;
14mod traits;
15
16#[cfg(test)]
17mod tests;
18
19pub use self::{
20    bytecode::DropKeep,
21    code_map::FuncBody,
22    config::Config,
23    func_builder::{FuncBuilder, FunctionBuilderAllocations, Instr, TranslationError},
24    resumable::{ResumableCall, ResumableInvocation, TypedResumableCall, TypedResumableInvocation},
25    stack::StackLimits,
26    traits::{CallParams, CallResults},
27};
28use self::{
29    bytecode::Instruction,
30    cache::InstanceCache,
31    code_map::CodeMap,
32    executor::execute_frame,
33    func_types::FuncTypeRegistry,
34    resumable::ResumableCallBase,
35    stack::{FuncFrame, Stack, ValueStack},
36};
37pub(crate) use self::{
38    func_args::{FuncParams, FuncResults},
39    func_types::DedupFuncType,
40};
41use super::{func::FuncEntityInternal, AsContextMut, Func};
42use crate::{
43    core::{Trap, TrapCode},
44    FuncType,
45};
46use alloc::{sync::Arc, vec::Vec};
47use core::sync::atomic::{AtomicU32, Ordering};
48use spin::{Mutex, RwLock};
49use wasmi_arena::{ArenaIndex, GuardedEntity};
50
51/// The outcome of a `wasmi` function execution.
52#[derive(Debug, Copy, Clone)]
53pub enum CallOutcome {
54    /// The function has returned.
55    Return,
56    /// The function called another function.
57    NestedCall(Func),
58}
59
60/// A unique engine index.
61///
62/// # Note
63///
64/// Used to protect against invalid entity indices.
65#[derive(Debug, Copy, Clone, PartialEq, Eq)]
66pub struct EngineIdx(u32);
67
68impl ArenaIndex for EngineIdx {
69    fn into_usize(self) -> usize {
70        self.0 as _
71    }
72
73    fn from_usize(value: usize) -> Self {
74        let value = value.try_into().unwrap_or_else(|error| {
75            panic!("index {value} is out of bounds as engine index: {error}")
76        });
77        Self(value)
78    }
79}
80
81impl EngineIdx {
82    /// Returns a new unique [`EngineIdx`].
83    fn new() -> Self {
84        /// A static store index counter.
85        static CURRENT_STORE_IDX: AtomicU32 = AtomicU32::new(0);
86        let next_idx = CURRENT_STORE_IDX.fetch_add(1, Ordering::AcqRel);
87        Self(next_idx)
88    }
89}
90
91/// An entity owned by the [`Engine`].
92type Guarded<Idx> = GuardedEntity<EngineIdx, Idx>;
93
94/// The `wasmi` interpreter.
95///
96/// # Note
97///
98/// - The current `wasmi` engine implements a bytecode interpreter.
99/// - This structure is intentionally cheap to copy.
100///   Most of its API has a `&self` receiver, so can be shared easily.
101#[derive(Debug, Clone)]
102pub struct Engine {
103    inner: Arc<EngineInner>,
104}
105
106impl Default for Engine {
107    fn default() -> Self {
108        Self::new(&Config::default())
109    }
110}
111
112impl Engine {
113    /// Creates a new [`Engine`] with default configuration.
114    ///
115    /// # Note
116    ///
117    /// Users should ues [`Engine::default`] to construct a default [`Engine`].
118    pub fn new(config: &Config) -> Self {
119        Self {
120            inner: Arc::new(EngineInner::new(config)),
121        }
122    }
123
124    /// Returns a shared reference to the [`Config`] of the [`Engine`].
125    pub fn config(&self) -> Config {
126        self.inner.config()
127    }
128
129    /// Allocates a new function type to the engine.
130    pub(super) fn alloc_func_type(&self, func_type: FuncType) -> DedupFuncType {
131        self.inner.alloc_func_type(func_type)
132    }
133
134    /// Resolves a deduplicated function type into a [`FuncType`] entity.
135    ///
136    /// # Panics
137    ///
138    /// - If the deduplicated function type is not owned by the engine.
139    /// - If the deduplicated function type cannot be resolved to its entity.
140    pub(super) fn resolve_func_type<F, R>(&self, func_type: DedupFuncType, f: F) -> R
141    where
142        F: FnOnce(&FuncType) -> R,
143    {
144        self.inner.resolve_func_type(func_type, f)
145    }
146
147    /// Allocates the instructions of a Wasm function body to the [`Engine`].
148    ///
149    /// Returns a [`FuncBody`] reference to the allocated function body.
150    pub(super) fn alloc_func_body<I>(
151        &self,
152        len_locals: usize,
153        max_stack_height: usize,
154        insts: I,
155    ) -> FuncBody
156    where
157        I: IntoIterator<Item = Instruction>,
158        I::IntoIter: ExactSizeIterator,
159    {
160        self.inner
161            .alloc_func_body(len_locals, max_stack_height, insts)
162    }
163
164    /// Resolves the [`FuncBody`] to the underlying `wasmi` bytecode instructions.
165    ///
166    /// # Note
167    ///
168    /// - This API is mainly intended for unit testing purposes and shall not be used
169    ///   outside of this context. The function bodies are intended to be data private
170    ///   to the `wasmi` interpreter.
171    ///
172    /// # Panics
173    ///
174    /// If the [`FuncBody`] is invalid for the [`Engine`].
175    #[cfg(test)]
176    pub(crate) fn resolve_inst(&self, func_body: FuncBody, index: usize) -> Option<Instruction> {
177        self.inner.resolve_inst(func_body, index)
178    }
179
180    /// Executes the given [`Func`] with parameters `params`.
181    ///
182    /// Stores the execution result into `results` upon a successful execution.
183    ///
184    /// # Note
185    ///
186    /// - Assumes that the `params` and `results` are well typed.
187    ///   Type checks are done at the [`Func::call`] API or when creating
188    ///   a new [`TypedFunc`] instance via [`Func::typed`].
189    /// - The `params` out parameter is in a valid but unspecified state if this
190    ///   function returns with an error.
191    ///
192    /// # Errors
193    ///
194    /// - If `params` are overflowing or underflowing the expected amount of parameters.
195    /// - If the given `results` do not match the the length of the expected results of `func`.
196    /// - When encountering a Wasm or host trap during the execution of `func`.
197    ///
198    /// [`TypedFunc`]: [`crate::TypedFunc`]
199    pub(crate) fn execute_func<Results>(
200        &self,
201        ctx: impl AsContextMut,
202        func: Func,
203        params: impl CallParams,
204        results: Results,
205    ) -> Result<<Results as CallResults>::Results, Trap>
206    where
207        Results: CallResults,
208    {
209        self.inner.execute_func(ctx, func, params, results)
210    }
211
212    /// Executes the given [`Func`] resumably with parameters `params` and returns.
213    ///
214    /// Stores the execution result into `results` upon a successful execution.
215    /// If the execution encounters a host trap it will return a handle to the user
216    /// that allows to resume the execution at that point.
217    ///
218    /// # Note
219    ///
220    /// - Assumes that the `params` and `results` are well typed.
221    ///   Type checks are done at the [`Func::call`] API or when creating
222    ///   a new [`TypedFunc`] instance via [`Func::typed`].
223    /// - The `params` out parameter is in a valid but unspecified state if this
224    ///   function returns with an error.
225    ///
226    /// # Errors
227    ///
228    /// - If `params` are overflowing or underflowing the expected amount of parameters.
229    /// - If the given `results` do not match the the length of the expected results of `func`.
230    /// - When encountering a Wasm trap during the execution of `func`.
231    /// - When `func` is a host function that traps.
232    ///
233    /// [`TypedFunc`]: [`crate::TypedFunc`]
234    pub(crate) fn execute_func_resumable<Results>(
235        &self,
236        ctx: impl AsContextMut,
237        func: Func,
238        params: impl CallParams,
239        results: Results,
240    ) -> Result<ResumableCallBase<<Results as CallResults>::Results>, Trap>
241    where
242        Results: CallResults,
243    {
244        self.inner
245            .execute_func_resumable(ctx, func, params, results)
246    }
247
248    /// Resumes the given `invocation` given the `params`.
249    ///
250    /// Stores the execution result into `results` upon a successful execution.
251    /// If the execution encounters a host trap it will return a handle to the user
252    /// that allows to resume the execution at that point.
253    ///
254    /// # Note
255    ///
256    /// - Assumes that the `params` and `results` are well typed.
257    ///   Type checks are done at the [`Func::call`] API or when creating
258    ///   a new [`TypedFunc`] instance via [`Func::typed`].
259    /// - The `params` out parameter is in a valid but unspecified state if this
260    ///   function returns with an error.
261    ///
262    /// # Errors
263    ///
264    /// - If `params` are overflowing or underflowing the expected amount of parameters.
265    /// - If the given `results` do not match the the length of the expected results of `func`.
266    /// - When encountering a Wasm trap during the execution of `func`.
267    /// - When `func` is a host function that traps.
268    ///
269    /// [`TypedFunc`]: [`crate::TypedFunc`]
270    pub(crate) fn resume_func<Results>(
271        &self,
272        ctx: impl AsContextMut,
273        invocation: ResumableInvocation,
274        params: impl CallParams,
275        results: Results,
276    ) -> Result<ResumableCallBase<<Results as CallResults>::Results>, Trap>
277    where
278        Results: CallResults,
279    {
280        self.inner.resume_func(ctx, invocation, params, results)
281    }
282
283    /// Recycles the given [`Stack`] for reuse in the [`Engine`].
284    pub(crate) fn recycle_stack(&self, stack: Stack) {
285        self.inner.recycle_stack(stack)
286    }
287}
288
289/// The internal state of the `wasmi` engine.
290#[derive(Debug)]
291pub struct EngineInner {
292    /// Engine resources shared across multiple engine executors.
293    res: RwLock<EngineResources>,
294    /// Reusable engine stacks for Wasm execution.
295    ///
296    /// Concurrently executing Wasm executions each require their own stack to
297    /// operate on. Therefore a Wasm engine is required to provide stacks and
298    /// ideally recycles old ones since creation of a new stack is rather expensive.
299    stacks: Mutex<EngineStacks>,
300}
301
302/// The engine's stacks for reuse.
303///
304/// Rquired for efficient concurrent Wasm executions.
305#[derive(Debug)]
306pub struct EngineStacks {
307    /// Stacks to be (re)used.
308    stacks: Vec<Stack>,
309    /// Stack limits for newly constructed engine stacks.
310    limits: StackLimits,
311    /// How many stacks should be kept for reuse at most.
312    keep: usize,
313}
314
315impl EngineStacks {
316    /// Creates new [`EngineStacks`] with the given [`StackLimits`].
317    pub fn new(config: &Config) -> Self {
318        Self {
319            stacks: Vec::new(),
320            limits: config.stack_limits(),
321            keep: config.cached_stacks(),
322        }
323    }
324
325    /// Reuse or create a new [`Stack`] if none was available.
326    pub fn reuse_or_new(&mut self) -> Stack {
327        match self.stacks.pop() {
328            Some(stack) => stack,
329            None => Stack::new(self.limits),
330        }
331    }
332
333    /// Disose and recycle the `stack`.
334    pub fn recycle(&mut self, stack: Stack) {
335        if !stack.is_empty() && self.stacks.len() < self.keep {
336            self.stacks.push(stack);
337        }
338    }
339}
340
341impl EngineInner {
342    /// Creates a new [`EngineInner`] with the given [`Config`].
343    fn new(config: &Config) -> Self {
344        Self {
345            res: RwLock::new(EngineResources::new(config)),
346            stacks: Mutex::new(EngineStacks::new(config)),
347        }
348    }
349
350    fn config(&self) -> Config {
351        self.res.read().config
352    }
353
354    fn alloc_func_type(&self, func_type: FuncType) -> DedupFuncType {
355        self.res.write().func_types.alloc_func_type(func_type)
356    }
357
358    fn alloc_func_body<I>(&self, len_locals: usize, max_stack_height: usize, insts: I) -> FuncBody
359    where
360        I: IntoIterator<Item = Instruction>,
361        I::IntoIter: ExactSizeIterator,
362    {
363        self.res
364            .write()
365            .code_map
366            .alloc(len_locals, max_stack_height, insts)
367    }
368
369    fn resolve_func_type<F, R>(&self, func_type: DedupFuncType, f: F) -> R
370    where
371        F: FnOnce(&FuncType) -> R,
372    {
373        f(self.res.read().func_types.resolve_func_type(func_type))
374    }
375
376    #[cfg(test)]
377    fn resolve_inst(&self, func_body: FuncBody, index: usize) -> Option<Instruction> {
378        self.res
379            .read()
380            .code_map
381            .get_instr(func_body, index)
382            .copied()
383    }
384
385    fn execute_func<Results>(
386        &self,
387        ctx: impl AsContextMut,
388        func: Func,
389        params: impl CallParams,
390        results: Results,
391    ) -> Result<<Results as CallResults>::Results, Trap>
392    where
393        Results: CallResults,
394    {
395        let res = self.res.read();
396        let mut stack = self.stacks.lock().reuse_or_new();
397        let results = EngineExecutor::new(&res, &mut stack)
398            .execute_func(ctx, func, params, results)
399            .map_err(TaggedTrap::into_trap);
400        self.stacks.lock().recycle(stack);
401        results
402    }
403
404    fn execute_func_resumable<Results>(
405        &self,
406        mut ctx: impl AsContextMut,
407        func: Func,
408        params: impl CallParams,
409        results: Results,
410    ) -> Result<ResumableCallBase<<Results as CallResults>::Results>, Trap>
411    where
412        Results: CallResults,
413    {
414        let res = self.res.read();
415        let mut stack = self.stacks.lock().reuse_or_new();
416        let results = EngineExecutor::new(&res, &mut stack).execute_func(
417            ctx.as_context_mut(),
418            func,
419            params,
420            results,
421        );
422        match results {
423            Ok(results) => {
424                self.stacks.lock().recycle(stack);
425                Ok(ResumableCallBase::Finished(results))
426            }
427            Err(TaggedTrap::Wasm(trap)) => {
428                self.stacks.lock().recycle(stack);
429                Err(trap)
430            }
431            Err(TaggedTrap::Host {
432                host_func,
433                host_trap,
434            }) => Ok(ResumableCallBase::Resumable(ResumableInvocation::new(
435                ctx.as_context().store.engine().clone(),
436                func,
437                host_func,
438                host_trap,
439                stack,
440            ))),
441        }
442    }
443
444    pub(crate) fn resume_func<Results>(
445        &self,
446        ctx: impl AsContextMut,
447        mut invocation: ResumableInvocation,
448        params: impl CallParams,
449        results: Results,
450    ) -> Result<ResumableCallBase<<Results as CallResults>::Results>, Trap>
451    where
452        Results: CallResults,
453    {
454        let res = self.res.read();
455        let host_func = invocation.host_func();
456        let results = EngineExecutor::new(&res, &mut invocation.stack)
457            .resume_func(ctx, host_func, params, results);
458        match results {
459            Ok(results) => {
460                self.stacks.lock().recycle(invocation.take_stack());
461                Ok(ResumableCallBase::Finished(results))
462            }
463            Err(TaggedTrap::Wasm(trap)) => {
464                self.stacks.lock().recycle(invocation.take_stack());
465                Err(trap)
466            }
467            Err(TaggedTrap::Host {
468                host_func,
469                host_trap,
470            }) => {
471                invocation.update(host_func, host_trap);
472                Ok(ResumableCallBase::Resumable(invocation))
473            }
474        }
475    }
476
477    fn recycle_stack(&self, stack: Stack) {
478        self.stacks.lock().recycle(stack);
479    }
480}
481
482/// Engine resources that are immutable during function execution.
483///
484/// Can be shared by multiple engine executors.
485#[derive(Debug)]
486pub struct EngineResources {
487    /// The configuration with which the [`Engine`] has been created.
488    config: Config,
489    /// Stores all Wasm function bodies that the interpreter is aware of.
490    code_map: CodeMap,
491    /// Deduplicated function types.
492    ///
493    /// # Note
494    ///
495    /// The engine deduplicates function types to make the equality
496    /// comparison very fast. This helps to speed up indirect calls.
497    func_types: FuncTypeRegistry,
498}
499
500impl EngineResources {
501    /// Creates a new [`EngineResources`] with the given [`Config`].
502    fn new(config: &Config) -> Self {
503        let engine_idx = EngineIdx::new();
504        Self {
505            config: *config,
506            code_map: CodeMap::default(),
507            func_types: FuncTypeRegistry::new(engine_idx),
508        }
509    }
510}
511
512/// Either a Wasm trap or a host trap with its originating host [`Func`].
513#[derive(Debug)]
514enum TaggedTrap {
515    /// The trap is originating from Wasm.
516    Wasm(Trap),
517    /// The trap is originating from a host function.
518    Host { host_func: Func, host_trap: Trap },
519}
520
521impl TaggedTrap {
522    /// Creates a [`TaggedTrap`] from a host error.
523    pub fn host(host_func: Func, host_trap: Trap) -> Self {
524        Self::Host {
525            host_func,
526            host_trap,
527        }
528    }
529
530    /// Returns the [`Trap`] of the [`TaggedTrap`].
531    pub fn into_trap(self) -> Trap {
532        match self {
533            TaggedTrap::Wasm(trap) => trap,
534            TaggedTrap::Host { host_trap, .. } => host_trap,
535        }
536    }
537}
538
539impl From<Trap> for TaggedTrap {
540    fn from(trap: Trap) -> Self {
541        Self::Wasm(trap)
542    }
543}
544
545impl From<TrapCode> for TaggedTrap {
546    fn from(trap_code: TrapCode) -> Self {
547        Self::Wasm(trap_code.into())
548    }
549}
550
551/// The internal state of the `wasmi` engine.
552#[derive(Debug)]
553pub struct EngineExecutor<'engine> {
554    /// Shared and reusable generic engine resources.
555    res: &'engine EngineResources,
556    /// The value and call stacks.
557    stack: &'engine mut Stack,
558}
559
560impl<'engine> EngineExecutor<'engine> {
561    /// Creates a new [`EngineExecutor`] with the given [`StackLimits`].
562    fn new(res: &'engine EngineResources, stack: &'engine mut Stack) -> Self {
563        Self { res, stack }
564    }
565
566    /// Executes the given [`Func`] using the given `params`.
567    ///
568    /// Stores the execution result into `results` upon a successful execution.
569    ///
570    /// # Errors
571    ///
572    /// - If the given `params` do not match the expected parameters of `func`.
573    /// - If the given `results` do not match the the length of the expected results of `func`.
574    /// - When encountering a Wasm or host trap during the execution of `func`.
575    fn execute_func<Results>(
576        &mut self,
577        mut ctx: impl AsContextMut,
578        func: Func,
579        params: impl CallParams,
580        results: Results,
581    ) -> Result<<Results as CallResults>::Results, TaggedTrap>
582    where
583        Results: CallResults,
584    {
585        self.initialize_args(params);
586        match func.as_internal(ctx.as_context()) {
587            FuncEntityInternal::Wasm(wasm_func) => {
588                let mut frame = self.stack.call_wasm_root(wasm_func, &self.res.code_map)?;
589                let mut cache = InstanceCache::from(frame.instance());
590                self.execute_wasm_func(ctx.as_context_mut(), &mut frame, &mut cache)?;
591            }
592            FuncEntityInternal::Host(host_func) => {
593                let host_func = host_func.clone();
594                self.stack
595                    .call_host_root(ctx.as_context_mut(), host_func, &self.res.func_types)?;
596            }
597        };
598        let results = self.write_results_back(results);
599        Ok(results)
600    }
601
602    /// Resumes the execution of the given [`Func`] using `params`.
603    ///
604    /// Stores the execution result into `results` upon a successful execution.
605    ///
606    /// # Errors
607    ///
608    /// - If the given `params` do not match the expected parameters of `func`.
609    /// - If the given `results` do not match the the length of the expected results of `func`.
610    /// - When encountering a Wasm or host trap during the execution of `func`.
611    fn resume_func<Results>(
612        &mut self,
613        mut ctx: impl AsContextMut,
614        host_func: Func,
615        params: impl CallParams,
616        results: Results,
617    ) -> Result<<Results as CallResults>::Results, TaggedTrap>
618    where
619        Results: CallResults,
620    {
621        self.stack
622            .values
623            .drop(host_func.ty(ctx.as_context()).params().len());
624        self.stack.values.extend(params.call_params());
625        let mut frame = self
626            .stack
627            .pop_frame()
628            .expect("a frame must be on the call stack upon resumption");
629        let mut cache = InstanceCache::from(frame.instance());
630        self.execute_wasm_func(ctx.as_context_mut(), &mut frame, &mut cache)?;
631        let results = self.write_results_back(results);
632        Ok(results)
633    }
634
635    /// Initializes the value stack with the given arguments `params`.
636    fn initialize_args(&mut self, params: impl CallParams) {
637        self.stack.clear();
638        self.stack.values.extend(params.call_params());
639    }
640
641    /// Writes the results of the function execution back into the `results` buffer.
642    ///
643    /// # Note
644    ///
645    /// The value stack is empty after this operation.
646    ///
647    /// # Panics
648    ///
649    /// - If the `results` buffer length does not match the remaining amount of stack values.
650    fn write_results_back<Results>(&mut self, results: Results) -> <Results as CallResults>::Results
651    where
652        Results: CallResults,
653    {
654        results.call_results(self.stack.values.drain())
655    }
656
657    /// Executes the top most Wasm function on the [`Stack`] until the [`Stack`] is empty.
658    ///
659    /// # Errors
660    ///
661    /// When encountering a Wasm or host trap during the execution of `func`.
662    fn execute_wasm_func(
663        &mut self,
664        mut ctx: impl AsContextMut,
665        frame: &mut FuncFrame,
666        cache: &mut InstanceCache,
667    ) -> Result<(), TaggedTrap> {
668        'outer: loop {
669            match self.execute_frame(ctx.as_context_mut(), frame, cache)? {
670                CallOutcome::Return => match self.stack.return_wasm() {
671                    Some(caller) => {
672                        *frame = caller;
673                        continue 'outer;
674                    }
675                    None => return Ok(()),
676                },
677                CallOutcome::NestedCall(called_func) => {
678                    match called_func.as_internal(ctx.as_context()) {
679                        FuncEntityInternal::Wasm(wasm_func) => {
680                            *frame = self.stack.call_wasm(frame, wasm_func, &self.res.code_map)?;
681                        }
682                        FuncEntityInternal::Host(host_func) => {
683                            cache.reset_default_memory_bytes();
684                            let host_func = host_func.clone();
685                            self.stack
686                                .call_host(
687                                    ctx.as_context_mut(),
688                                    frame,
689                                    host_func,
690                                    &self.res.func_types,
691                                )
692                                .or_else(|trap| {
693                                    // Push the calling function onto the Stack to make it possible to resume execution.
694                                    self.stack.push_frame(*frame)?;
695                                    Err(TaggedTrap::host(called_func, trap))
696                                })?;
697                        }
698                    }
699                }
700            }
701        }
702    }
703
704    /// Executes the given function `frame` and returns the result.
705    ///
706    /// # Errors
707    ///
708    /// - If the execution of the function `frame` trapped.
709    #[inline(always)]
710    fn execute_frame(
711        &mut self,
712        mut ctx: impl AsContextMut,
713        frame: &mut FuncFrame,
714        cache: &mut InstanceCache,
715    ) -> Result<CallOutcome, Trap> {
716        /// Converts a [`TrapCode`] into a [`Trap`].
717        ///
718        /// This function exists for performance reasons since its `#[cold]`
719        /// annotation has severe effects on performance.
720        #[inline]
721        #[cold]
722        fn make_trap(code: TrapCode) -> Trap {
723            code.into()
724        }
725
726        let value_stack = &mut self.stack.values;
727        execute_frame(
728            &mut ctx.as_context_mut().store.inner,
729            value_stack,
730            cache,
731            frame,
732        )
733        .map_err(make_trap)
734    }
735}