wasmtime/runtime/store/func_refs.rs
1//! Lifetime management of `VMFuncRef`s inside of stores, and filling in their
2//! trampolines.
3
4use crate::Definition;
5use crate::module::ModuleRegistry;
6use crate::prelude::*;
7use crate::runtime::HostFunc;
8use crate::runtime::vm::{AlwaysMut, SendSyncPtr, VMArrayCallHostFuncContext, VMFuncRef};
9use alloc::sync::Arc;
10use core::mem::size_of;
11use 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)]
19pub 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.
37enum 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
81impl 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.
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.
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.
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.
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.
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.
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.
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.
193unsafe 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}