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