wasmer_wasix/state/handles/
mod.rs

1mod global;
2mod thread_local;
3
4#[cfg(any(feature = "sys", feature = "sys-minimal"))]
5pub(crate) use global::*;
6#[cfg(feature = "js")]
7pub(crate) use thread_local::*;
8
9use tracing::{error, trace};
10use wasmer::{
11    AsStoreMut, AsStoreRef, Function, FunctionEnvMut, Global, Instance, Memory, MemoryView, Module,
12    Table, TypedFunction, Value,
13};
14use wasmer_wasix_types::wasi::Errno;
15
16use crate::{WasiEnv, state::LinkError};
17
18use super::Linker;
19
20/// Various [`TypedFunction`] and [`Global`] handles for an active WASI(X) instance.
21///
22/// Used to access and modify runtime state.
23// TODO: make fields private
24#[derive(Debug, Clone)]
25pub struct WasiModuleInstanceHandles {
26    // TODO: the two fields below are instance specific, while all others are module specific.
27    // Should be split up.
28    /// Represents a reference to the memory
29    pub(crate) memory: Memory,
30    pub(crate) instance: wasmer::Instance,
31
32    /// Points to the indirect function table
33    pub(crate) indirect_function_table: Option<Table>,
34
35    /// Points to the current location of the memory stack pointer
36    pub(crate) stack_pointer: Option<Global>,
37
38    /// Points to the end of the data section
39    pub(crate) data_end: Option<Global>,
40
41    /// Points to the lower end of the stack
42    pub(crate) stack_low: Option<Global>,
43
44    /// Points to the higher end of the stack
45    pub(crate) stack_high: Option<Global>,
46
47    /// Points to the start of the TLS area
48    pub(crate) tls_base: Option<Global>,
49
50    /// Main function that will be invoked (name = "_start")
51    pub(crate) start: Option<TypedFunction<(), ()>>,
52
53    /// Function thats invoked to initialize the WASM module (name = "_initialize")
54    // TODO: review allow...
55    #[allow(dead_code)]
56    pub(crate) initialize: Option<TypedFunction<(), ()>>,
57
58    /// Represents the callback for spawning a thread (name = "wasi_thread_start")
59    /// (due to limitations with i64 in browsers the parameters are broken into i32 pairs)
60    /// [this takes a user_data field]
61    pub(crate) thread_spawn: Option<TypedFunction<(i32, i32), ()>>,
62
63    /// Represents the callback for signals (name = "__wasm_signal")
64    /// Signals are triggered asynchronously at idle times of the process
65    // TODO: why is this here? It can exist in WasiEnv
66    pub(crate) signal: Option<TypedFunction<i32, ()>>,
67
68    /// Flag that indicates if the signal callback has been set by the WASM
69    /// process - if it has not been set then the runtime behaves differently
70    /// when a CTRL-C is pressed.
71    pub(crate) signal_set: bool,
72
73    /// Flag that indicates if the stack capture exports are being used by
74    /// this WASM process which means that it will be using asyncify
75    pub(crate) has_stack_checkpoint: bool,
76
77    /// asyncify_start_unwind(data : i32): call this to start unwinding the
78    /// stack from the current location. "data" must point to a data
79    /// structure as described above (with fields containing valid data).
80    // TODO: review allow...
81    #[allow(dead_code)]
82    pub(crate) asyncify_start_unwind: Option<TypedFunction<i32, ()>>,
83
84    /// asyncify_stop_unwind(): call this to note that unwinding has
85    /// concluded. If no other code will run before you start to rewind,
86    /// this is not strictly necessary, however, if you swap between
87    /// coroutines, or even just want to run some normal code during a
88    /// "sleep", then you must call this at the proper time. Otherwise,
89    /// the code will think it is still unwinding when it should not be,
90    /// which means it will keep unwinding in a meaningless way.
91    // TODO: review allow...
92    #[allow(dead_code)]
93    pub(crate) asyncify_stop_unwind: Option<TypedFunction<(), ()>>,
94
95    /// asyncify_start_rewind(data : i32): call this to start rewinding the
96    /// stack vack up to the location stored in the provided data. This prepares
97    /// for the rewind; to start it, you must call the first function in the
98    /// call stack to be unwound.
99    // TODO: review allow...
100    #[allow(dead_code)]
101    pub(crate) asyncify_start_rewind: Option<TypedFunction<i32, ()>>,
102
103    /// asyncify_stop_rewind(): call this to note that rewinding has
104    /// concluded, and normal execution can resume.
105    // TODO: review allow...
106    #[allow(dead_code)]
107    pub(crate) asyncify_stop_rewind: Option<TypedFunction<(), ()>>,
108
109    /// asyncify_get_state(): call this to get the current value of the
110    /// internal "__asyncify_state" variable as described above.
111    /// It can be used to distinguish between unwinding/rewinding and normal
112    /// calls, so that you know when to start an asynchronous operation and
113    /// when to propagate results back.
114    #[allow(dead_code)]
115    pub(crate) asyncify_get_state: Option<TypedFunction<(), i32>>,
116}
117
118impl WasiModuleInstanceHandles {
119    pub fn new(
120        memory: Memory,
121        store: &impl AsStoreRef,
122        instance: Instance,
123        indirect_function_table: Option<Table>,
124    ) -> Self {
125        let has_stack_checkpoint = instance
126            .module()
127            .imports()
128            .any(|f| f.name() == "stack_checkpoint");
129        Self {
130            memory,
131            indirect_function_table: indirect_function_table.or_else(|| {
132                instance
133                    .exports
134                    .get_table("__indirect_function_table")
135                    .cloned()
136                    .ok()
137            }),
138            stack_pointer: instance.exports.get_global("__stack_pointer").cloned().ok(),
139            data_end: instance.exports.get_global("__data_end").cloned().ok(),
140            stack_low: instance.exports.get_global("__stack_low").cloned().ok(),
141            stack_high: instance.exports.get_global("__stack_high").cloned().ok(),
142            tls_base: instance.exports.get_global("__tls_base").cloned().ok(),
143            start: instance.exports.get_typed_function(store, "_start").ok(),
144            initialize: instance
145                .exports
146                .get_typed_function(store, "_initialize")
147                .ok(),
148            thread_spawn: instance
149                .exports
150                .get_typed_function(store, "wasi_thread_start")
151                .ok(),
152            signal: instance
153                .exports
154                .get_typed_function(&store, "__wasm_signal")
155                .ok(),
156            has_stack_checkpoint,
157            signal_set: false,
158            asyncify_start_unwind: instance
159                .exports
160                .get_typed_function(store, "asyncify_start_unwind")
161                .ok(),
162            asyncify_stop_unwind: instance
163                .exports
164                .get_typed_function(store, "asyncify_stop_unwind")
165                .ok(),
166            asyncify_start_rewind: instance
167                .exports
168                .get_typed_function(store, "asyncify_start_rewind")
169                .ok(),
170            asyncify_stop_rewind: instance
171                .exports
172                .get_typed_function(store, "asyncify_stop_rewind")
173                .ok(),
174            asyncify_get_state: instance
175                .exports
176                .get_typed_function(store, "asyncify_get_state")
177                .ok(),
178            instance,
179        }
180    }
181
182    pub fn module(&self) -> &Module {
183        self.instance.module()
184    }
185
186    pub fn module_clone(&self) -> Module {
187        self.instance.module().clone()
188    }
189
190    /// Providers safe access to the memory
191    /// (it must be initialized before it can be used)
192    pub fn memory_view<'a>(&'a self, store: &'a (impl AsStoreRef + ?Sized)) -> MemoryView<'a> {
193        self.memory.view(store)
194    }
195
196    /// Providers safe access to the memory
197    /// (it must be initialized before it can be used)
198    pub fn memory(&self) -> &Memory {
199        &self.memory
200    }
201
202    /// Copy the lazy reference so that when it's initialized during the
203    /// export phase, all the other references get a copy of it
204    pub fn memory_clone(&self) -> Memory {
205        self.memory.clone()
206    }
207
208    pub fn instance(&self) -> &Instance {
209        &self.instance
210    }
211}
212
213#[derive(Debug, Clone)]
214pub enum WasiModuleTreeHandles {
215    Static(WasiModuleInstanceHandles),
216    Dynamic {
217        linker: Linker,
218        main_module_instance_handles: WasiModuleInstanceHandles,
219    },
220}
221
222#[derive(thiserror::Error, Debug, Clone)]
223pub enum FunctionLookupError {
224    #[error("No function found at the requested index `{0}`")]
225    /// Function not found at the index
226    Empty(u32),
227    #[error("Table index `{0}` out of bounds")]
228    /// Table index out of bounds
229    OutOfBounds(u32),
230    #[error("The table does not contain functions")]
231    /// The table does not contain functions
232    NotAFunctionTable,
233    #[error("No indirect function table available")]
234    /// No indirect function table available
235    NoIndirectFunctionTable,
236}
237impl From<FunctionLookupError> for Errno {
238    fn from(e: FunctionLookupError) -> Self {
239        match e {
240            FunctionLookupError::Empty(_) => Errno::Inval,
241            FunctionLookupError::OutOfBounds(_) => Errno::Inval,
242            FunctionLookupError::NotAFunctionTable => Errno::Inval,
243            FunctionLookupError::NoIndirectFunctionTable => Errno::Notsup,
244        }
245    }
246}
247
248impl WasiModuleTreeHandles {
249    /// Can be used to get the `WasiModuleInstanceHandles` of the main module.
250    /// If access to the side modules' instance handles is required, one must go
251    /// through the `Linker` to retrieve the one they need.
252    pub(crate) fn main_module_instance_handles(&self) -> &WasiModuleInstanceHandles {
253        match self {
254            WasiModuleTreeHandles::Static(handles) => handles,
255            WasiModuleTreeHandles::Dynamic {
256                main_module_instance_handles,
257                ..
258            } => main_module_instance_handles,
259        }
260    }
261
262    /// See comments on `main_module_instance_handles`.
263    pub(crate) fn main_module_instance_handles_mut(&mut self) -> &mut WasiModuleInstanceHandles {
264        match self {
265            WasiModuleTreeHandles::Static(handles) => handles,
266            WasiModuleTreeHandles::Dynamic {
267                main_module_instance_handles,
268                ..
269            } => main_module_instance_handles,
270        }
271    }
272
273    /// Helper function to get the instance handles of a static module, or fail otherwise.
274    /// See comments on ensure_static_module for more details.
275    pub(crate) fn static_module_instance_handles(&self) -> Option<&WasiModuleInstanceHandles> {
276        match self {
277            WasiModuleTreeHandles::Static(handles) => Some(handles),
278            WasiModuleTreeHandles::Dynamic { .. } => None,
279        }
280    }
281
282    /// See comments on `static_module_instance_handles`.
283    #[allow(dead_code)]
284    pub(crate) fn static_module_instance_handles_mut(
285        &mut self,
286    ) -> Option<&mut WasiModuleInstanceHandles> {
287        match self {
288            WasiModuleTreeHandles::Static(handles) => Some(handles),
289            WasiModuleTreeHandles::Dynamic { .. } => None,
290        }
291    }
292
293    /// Helper function to ensure the module isn't dynamically linked, needed since
294    /// we only support a subset of WASIX functionality for dynamically linked modules.
295    /// Specifically, anything that requires asyncify is not supported right now.
296    pub(crate) fn ensure_static_module(&self) -> Result<(), ()> {
297        match self {
298            WasiModuleTreeHandles::Static(_) => Ok(()),
299            _ => Err(()),
300        }
301    }
302
303    /// Providers safe access to the memory
304    /// (it must be initialized before it can be used)
305    pub fn memory_view<'a>(&'a self, store: &'a (impl AsStoreRef + ?Sized)) -> MemoryView<'a> {
306        self.main_module_instance_handles().memory.view(store)
307    }
308
309    /// Providers safe access to the memory
310    /// (it must be initialized before it can be used)
311    pub fn memory(&self) -> &Memory {
312        &self.main_module_instance_handles().memory
313    }
314
315    /// Copy the lazy reference so that when it's initialized during the
316    /// export phase, all the other references get a copy of it
317    pub fn memory_clone(&self) -> Memory {
318        self.main_module_instance_handles().memory.clone()
319    }
320
321    pub fn linker(&self) -> Option<&Linker> {
322        match self {
323            Self::Static(_) => None,
324            Self::Dynamic { linker, .. } => Some(linker),
325        }
326    }
327
328    /// Helper function to look up a function in the indirect function table
329    ///
330    /// * Returns an Errno if an error occurred.
331    /// * Returns `Ok(None)` if the index is out of bounds.
332    /// * Returns `Ok(Some(None))` if there is no function at the index.
333    /// * Returns `Ok(Some(Some(function)))` if there is a function at the index.
334    pub fn indirect_function_table_lookup(
335        &self,
336        store: &mut impl AsStoreMut,
337        index: u32,
338    ) -> Result<Function, FunctionLookupError> {
339        let value = self
340            .main_module_instance_handles()
341            .indirect_function_table
342            .as_ref()
343            .ok_or(FunctionLookupError::NoIndirectFunctionTable)?
344            .get(store, index);
345        let Some(value) = value else {
346            trace!(
347                function_id = index,
348                "Function not found in indirect function table"
349            );
350            return Err(FunctionLookupError::OutOfBounds(index));
351        };
352        let Value::FuncRef(funcref) = value else {
353            error!("Function table contains something other than a funcref");
354            return Err(FunctionLookupError::NotAFunctionTable);
355        };
356        let Some(funcref) = funcref else {
357            trace!(function_id = index, "No function at the supplied index");
358            return Err(FunctionLookupError::Empty(index));
359        };
360        Ok(funcref)
361    }
362
363    /// Check if an indirect_function_table entry is reserved for closures.
364    ///
365    /// Returns false if the entry is not reserved for closures.
366    pub fn is_closure(
367        ctx: &mut FunctionEnvMut<'_, WasiEnv>,
368        function_id: u32,
369    ) -> Result<bool, LinkError> {
370        // We need a manual deref here for the thread_local case.
371        // See: impl Deref for WasiInstanceGuard<'_> in thread_local.rs.
372        // Then, we need to allow the lint for the normal case.
373        #[allow(clippy::borrow_deref_ref)]
374        match &*ctx.data().inner() {
375            WasiModuleTreeHandles::Static(_) => Ok(false),
376            WasiModuleTreeHandles::Dynamic { linker, .. } => {
377                linker.clone().is_closure(function_id, ctx)
378            }
379        }
380    }
381}