wasm3/
runtime.rs

1use alloc::boxed::Box;
2use alloc::vec::Vec;
3use core::cell::UnsafeCell;
4use core::mem;
5use core::pin::Pin;
6use core::ptr::{self, NonNull};
7
8use crate::environment::Environment;
9use crate::error::{Error, Result};
10use crate::function::Function;
11use crate::module::{Module, ParsedModule};
12use crate::utils::eq_cstr_str;
13
14type PinnedAnyClosure = Pin<Box<dyn core::any::Any + 'static>>;
15
16/// A runtime context for wasm3 modules.
17#[derive(Debug)]
18pub struct Runtime {
19    raw: NonNull<ffi::M3Runtime>,
20    environment: Environment,
21    // holds all linked closures so that they properly get disposed of when runtime drops
22    closure_store: UnsafeCell<Vec<PinnedAnyClosure>>,
23    // holds all backing data of loaded modules as they have to be kept alive for the module's lifetime
24    module_data: UnsafeCell<Vec<Box<[u8]>>>,
25}
26
27impl Runtime {
28    /// Creates a new runtime with the given stack size in slots.
29    ///
30    /// # Errors
31    ///
32    /// This function will error on memory allocation failure.
33    pub fn new(environment: &Environment, stack_size: u32) -> Result<Self> {
34        unsafe {
35            NonNull::new(ffi::m3_NewRuntime(
36                environment.as_ptr(),
37                stack_size,
38                ptr::null_mut(),
39            ))
40        }
41        .ok_or_else(Error::malloc_error)
42        .map(|raw| Runtime {
43            raw,
44            environment: environment.clone(),
45            closure_store: UnsafeCell::new(Vec::new()),
46            module_data: UnsafeCell::new(Vec::new()),
47        })
48    }
49
50    /// Parses and loads a module from bytes.
51    pub fn parse_and_load_module<'rt, TData: Into<Box<[u8]>>>(
52        &'rt self,
53        bytes: TData,
54    ) -> Result<Module<'rt>> {
55        Module::parse(&self.environment, bytes).and_then(|module| self.load_module(module))
56    }
57
58    /// Loads a parsed module returning the module if unsuccessful.
59    ///
60    /// # Errors
61    ///
62    /// This function will error if the module's environment differs from the one this runtime uses.
63    pub fn load_module<'rt>(&'rt self, module: ParsedModule) -> Result<Module<'rt>> {
64        if &self.environment != module.environment() {
65            Err(Error::ModuleLoadEnvMismatch)
66        } else {
67            let raw_mod = module.as_ptr();
68            Error::from_ffi_res(unsafe { ffi::m3_LoadModule(self.raw.as_ptr(), raw_mod) })?;
69            // SAFETY: Runtime isn't Send, therefor this access is single-threaded and kept alive only for the Vec::push call
70            // as such this can not alias.
71            unsafe { (*self.module_data.get()).push(module.take_data()) };
72
73            Ok(Module::from_raw(self, raw_mod))
74        }
75    }
76
77    /// Looks up a function by the given name in the loaded modules of this runtime.
78    /// See [`Module::find_function`] for possible error cases.
79    ///
80    /// [`Module::find_function`]: ../module/struct.Module.html#method.find_function
81    pub fn find_function<'rt, ARGS, RET>(&'rt self, name: &str) -> Result<Function<'rt, ARGS, RET>>
82    where
83        ARGS: crate::WasmArgs,
84        RET: crate::WasmType,
85    {
86        self.modules()
87            .find_map(|module| match module.find_function::<ARGS, RET>(name) {
88                res @ (Ok(_) | Err(Error::InvalidFunctionSignature)) => Some(res),
89                _ => None,
90            })
91            .unwrap_or(Err(Error::FunctionNotFound))
92    }
93
94    /// Searches for a module with the given name in the runtime's loaded modules.
95    ///
96    /// Using this over searching through [`Runtime::modules`] is a bit more efficient as it
97    /// works on the underlying CStrings directly and doesn't require an upfront length calculation.
98    ///
99    /// [`Runtime::modules`]: struct.Runtime.html#method.modules
100    pub fn find_module<'rt>(&'rt self, name: &str) -> Result<Module<'rt>> {
101        unsafe {
102            let mut module = ptr::NonNull::new(self.raw.as_ref().modules);
103            while let Some(raw_mod) = module {
104                if eq_cstr_str(raw_mod.as_ref().name, name) {
105                    return Ok(Module::from_raw(self, raw_mod.as_ptr()));
106                }
107
108                module = ptr::NonNull::new(raw_mod.as_ref().next);
109            }
110            Err(Error::ModuleNotFound)
111        }
112    }
113
114    /// Returns an iterator over the runtime's loaded modules.
115    pub fn modules<'rt>(&'rt self) -> impl Iterator<Item = Module<'rt>> + 'rt {
116        // pointer could get invalidated if modules can become unloaded
117        // pushing new modules into the runtime while this iterator exists is fine as its backed by a linked list meaning it wont get invalidated.
118        let mut module = unsafe { ptr::NonNull::new(self.raw.as_ref().modules) };
119        core::iter::from_fn(move || {
120            let next = unsafe { module.and_then(|module| ptr::NonNull::new(module.as_ref().next)) };
121            mem::replace(&mut module, next).map(|raw| Module::from_raw(self, raw.as_ptr()))
122        })
123    }
124
125    /// Resizes the number of allocatable pages to num_pages.
126    ///
127    /// # Errors
128    ///
129    /// This function will error out if it failed to resize memory allocation.
130    pub fn resize_memory(&self, num_pages: u32) -> Result<()> {
131        Error::from_ffi_res(unsafe { ffi::ResizeMemory(self.raw.as_ptr(), num_pages) })
132    }
133
134    /// Returns the raw memory of this runtime.
135    ///
136    /// # Safety
137    ///
138    /// The returned pointer may get invalidated when wasm function objects are called due to reallocations.
139    pub unsafe fn memory(&self) -> *const [u8] {
140        let len = (*self.mallocated()).length as usize;
141        let data = if len == 0 {
142            ptr::NonNull::dangling().as_ptr()
143        } else {
144            self.mallocated().offset(1).cast()
145        };
146        ptr::slice_from_raw_parts(data, len)
147    }
148
149    /// Returns the raw memory of this runtime.
150    ///
151    /// # Safety
152    ///
153    /// The returned pointer may get invalidated when wasm function objects are called due to reallocations.
154    pub unsafe fn memory_mut(&self) -> *mut [u8] {
155        let len = (*self.mallocated()).length as usize;
156        let data = if len == 0 {
157            ptr::NonNull::dangling().as_ptr()
158        } else {
159            self.mallocated().offset(1).cast()
160        };
161        ptr::slice_from_raw_parts_mut(data, len)
162    }
163
164    /// Returns the stack of this runtime.
165    pub fn stack(&self) -> *const [ffi::m3slot_t] {
166        unsafe {
167            ptr::slice_from_raw_parts(
168                self.raw.as_ref().stack.cast::<ffi::m3slot_t>(),
169                self.raw.as_ref().numStackSlots as usize,
170            )
171        }
172    }
173
174    /// Returns the stack of this runtime.
175    pub fn stack_mut(&self) -> *mut [ffi::m3slot_t] {
176        unsafe {
177            ptr::slice_from_raw_parts_mut(
178                self.raw.as_ref().stack.cast::<ffi::m3slot_t>(),
179                self.raw.as_ref().numStackSlots as usize,
180            )
181        }
182    }
183}
184
185impl Runtime {
186    pub(crate) unsafe fn mallocated(&self) -> *mut ffi::M3MemoryHeader {
187        self.raw.as_ref().memory.mallocated
188    }
189
190    pub(crate) fn push_closure(&self, closure: PinnedAnyClosure) {
191        unsafe { (*self.closure_store.get()).push(closure) };
192    }
193
194    pub(crate) fn as_ptr(&self) -> ffi::IM3Runtime {
195        self.raw.as_ptr()
196    }
197}
198
199impl Drop for Runtime {
200    fn drop(&mut self) {
201        unsafe { ffi::m3_FreeRuntime(self.raw.as_ptr()) };
202    }
203}
204
205#[test]
206fn create_and_drop_rt() {
207    let env = Environment::new().expect("env alloc failure");
208    assert!(Runtime::new(&env, 1024 * 64).is_ok());
209}