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