1 //! Lifetime management of `VMFuncRef`s inside of stores, and filling in their
2 //! trampolines.
3 
4 use crate::Definition;
5 use crate::module::ModuleRegistry;
6 use crate::prelude::*;
7 use crate::runtime::HostFunc;
8 use crate::runtime::vm::{AlwaysMut, SendSyncPtr, VMArrayCallHostFuncContext, VMFuncRef};
9 use alloc::sync::Arc;
10 use core::mem::size_of;
11 use core::ptr::NonNull;
12 
13 /// An arena of `VMFuncRef`s.
14 ///
15 /// Allows a store to pin and own funcrefs so that it can patch in trampolines
16 /// for `VMFuncRef`s that are missing a `wasm_call` trampoline and
17 /// need Wasm to supply it.
18 #[derive(Default)]
19 pub struct FuncRefs {
20     /// A bump allocation arena where we allocate `VMFuncRef`s such
21     /// that they are pinned and owned.
22     bump: AlwaysMut<bumpalo::Bump>,
23 
24     /// Pointers into `self.bump` for entries that need `wasm_call` field filled
25     /// in.
26     with_holes: TryVec<SendSyncPtr<VMFuncRef>>,
27 
28     /// General-purpose storage of "function things" that need to live as long
29     /// as the entire store.
30     storage: TryVec<Storage>,
31 }
32 
33 /// Various items to place in `FuncRefs::storage`
34 ///
35 /// Note that each field has its own heap-level indirection to be resistant to
36 /// `FuncRefs::storage` having its own backing storage reallocated.
37 enum Storage {
38     /// Pinned arbitrary `Linker` definitions that must be kept alive for the
39     /// entire duration of the store. This can include host functions, funcrefs
40     /// inside them, etc.
41     InstancePreDefinitions {
42         #[expect(dead_code, reason = "only here to keep the original value alive")]
43         defs: Arc<TryVec<Definition>>,
44     },
45 
46     /// Pinned `VMFuncRef`s that had their `wasm_call` field
47     /// pre-patched when constructing an `InstancePre`, and which we need to
48     /// keep alive for our owning store's lifetime.
49     InstancePreFuncRefs {
50         #[expect(dead_code, reason = "only here to keep the original value alive")]
51         funcs: Arc<TryVec<VMFuncRef>>,
52     },
53 
54     /// A uniquely-owned host function within a `Store`. This comes about with
55     /// `Func::new` or similar APIs. The `HostFunc` internally owns the
56     /// `InstanceHandle` and that will get dropped when this `HostFunc` itself
57     /// is dropped.
58     ///
59     /// Note that this contains the vmctx that the `VMFuncRef` points to for
60     /// this host function.
61     BoxHost {
62         #[expect(dead_code, reason = "only here to keep the original value alive")]
63         func: Box<HostFunc>,
64     },
65 
66     /// A function is shared across possibly other stores, hence the `Arc`. This
67     /// variant happens when a `Linker`-defined function is instantiated within
68     /// a `Store` (e.g. via `Linker::get` or similar APIs). The `Arc` here
69     /// indicates that there's some number of other stores holding this function
70     /// too, so dropping this may not deallocate the underlying
71     /// `InstanceHandle`.
72     ///
73     /// Note that this contains the vmctx that the `VMFuncRef` points to for
74     /// this host function.
75     ArcHost {
76         #[expect(dead_code, reason = "only here to keep the original value alive")]
77         func: Arc<HostFunc>,
78     },
79 }
80 
81 impl FuncRefs {
82     /// Push the given `VMFuncRef` into this arena, returning a
83     /// pinned pointer to it.
84     ///
85     /// # Safety
86     ///
87     /// You may only access the return value on the same thread as this
88     /// `FuncRefs` and only while the store holding this `FuncRefs` exists.
89     /// Additionally the `vmctx` field of `func_ref` must be valid to read.
push( &mut self, func_ref: VMFuncRef, modules: &ModuleRegistry, ) -> Result<NonNull<VMFuncRef>, OutOfMemory>90     pub unsafe fn push(
91         &mut self,
92         func_ref: VMFuncRef,
93         modules: &ModuleRegistry,
94     ) -> Result<NonNull<VMFuncRef>, OutOfMemory> {
95         debug_assert!(func_ref.wasm_call.is_none());
96         let func_ref = self
97             .bump
98             .get_mut()
99             .try_alloc(func_ref)
100             .map_err(|_| OutOfMemory::new(size_of::<VMFuncRef>()))?;
101         // SAFETY: it's a contract of this function itself that `func_ref` has a
102         // valid vmctx field to read.
103         let has_hole = unsafe { !try_fill(func_ref, modules) };
104         let unpatched = SendSyncPtr::from(func_ref);
105         if has_hole {
106             self.with_holes.push(unpatched)?;
107         }
108         Ok(unpatched.as_non_null())
109     }
110 
111     /// Patch any `VMFuncRef::wasm_call`s that need filling in.
fill(&mut self, modules: &ModuleRegistry)112     pub fn fill(&mut self, modules: &ModuleRegistry) {
113         self.with_holes
114             .retain_mut(|f| unsafe { !try_fill(f.as_mut(), modules) });
115     }
116 
117     /// Reserves `amt` space for extra items in "storage" for this store.
reserve_storage(&mut self, amt: usize) -> Result<(), OutOfMemory>118     pub fn reserve_storage(&mut self, amt: usize) -> Result<(), OutOfMemory> {
119         self.storage.reserve(amt)
120     }
121 
122     /// Push pre-patched `VMFuncRef`s from an `InstancePre`.
123     ///
124     /// This is used to ensure that the store itself persists the entire list of
125     /// `funcs` for the entire lifetime of the store.
push_instance_pre_func_refs( &mut self, funcs: Arc<TryVec<VMFuncRef>>, ) -> Result<(), OutOfMemory>126     pub fn push_instance_pre_func_refs(
127         &mut self,
128         funcs: Arc<TryVec<VMFuncRef>>,
129     ) -> Result<(), OutOfMemory> {
130         self.storage.push(Storage::InstancePreFuncRefs { funcs })
131     }
132 
133     /// Push linker definitions into storage, keeping them alive for the entire
134     /// lifetime of the store.
135     ///
136     /// This is used to keep linker-defined functions' vmctx values alive, for
137     /// example.
push_instance_pre_definitions( &mut self, defs: Arc<TryVec<Definition>>, ) -> Result<(), OutOfMemory>138     pub fn push_instance_pre_definitions(
139         &mut self,
140         defs: Arc<TryVec<Definition>>,
141     ) -> Result<(), OutOfMemory> {
142         self.storage.push(Storage::InstancePreDefinitions { defs })
143     }
144 
145     /// Pushes a shared host function into this store.
146     ///
147     /// This will create a store-local `VMFuncRef` with a hole to fill in where
148     /// the `wasm_call` will get filled in as needed.
149     ///
150     /// This function returns a `VMFuncRef` which is store-local and will have
151     /// `wasm_call` filled in eventually if needed.
152     ///
153     /// # Safety
154     ///
155     /// You may only access the return value on the same thread as this
156     /// `FuncRefs` and only while the store holding this `FuncRefs` exists.
push_arc_host( &mut self, func: Arc<HostFunc>, modules: &ModuleRegistry, ) -> Result<NonNull<VMFuncRef>, OutOfMemory>157     pub fn push_arc_host(
158         &mut self,
159         func: Arc<HostFunc>,
160         modules: &ModuleRegistry,
161     ) -> Result<NonNull<VMFuncRef>, OutOfMemory> {
162         debug_assert!(func.func_ref().wasm_call.is_none());
163         // SAFETY: the vmctx field in the funcref of `HostFunc` is safe to read.
164         let ret = unsafe { self.push(func.func_ref().clone(), modules)? };
165         self.storage.push(Storage::ArcHost { func })?;
166         Ok(ret)
167     }
168 
169     /// Same as `push_arc_host`, but for owned host functions.
push_box_host( &mut self, func: Box<HostFunc>, modules: &ModuleRegistry, ) -> Result<NonNull<VMFuncRef>, OutOfMemory>170     pub fn push_box_host(
171         &mut self,
172         func: Box<HostFunc>,
173         modules: &ModuleRegistry,
174     ) -> Result<NonNull<VMFuncRef>, OutOfMemory> {
175         debug_assert!(func.func_ref().wasm_call.is_none());
176         // SAFETY: the vmctx field in the funcref of `HostFunc` is safe to read.
177         let ret = unsafe { self.push(func.func_ref().clone(), modules)? };
178         self.storage.push(Storage::BoxHost { func })?;
179         Ok(ret)
180     }
181 }
182 
183 /// Attempts to fill the `wasm_call` field of `func_ref` given `modules`
184 /// registered and returns `true` if the field was filled, `false` otherwise.
185 ///
186 /// # Panics
187 ///
188 /// Panics if `func_ref.wasm_call.is_some()`
189 ///
190 /// # Safety
191 ///
192 /// This relies on `func_ref` being a valid pointer with a valid `vmctx` field.
try_fill(func_ref: &mut VMFuncRef, modules: &ModuleRegistry) -> bool193 unsafe fn try_fill(func_ref: &mut VMFuncRef, modules: &ModuleRegistry) -> bool {
194     debug_assert!(func_ref.wasm_call.is_none());
195 
196     // Debug assert that the vmctx is a `VMArrayCallHostFuncContext` as
197     // that is the only kind that can have holes.
198     //
199     // SAFETY: the validity of `vmctx` is a contract of this function itself.
200     unsafe {
201         let _ = VMArrayCallHostFuncContext::from_opaque(func_ref.vmctx.as_non_null());
202     }
203 
204     func_ref.wasm_call = modules
205         .wasm_to_array_trampoline(func_ref.type_index)
206         .map(|f| f.into());
207     func_ref.wasm_call.is_some()
208 }
209