wasm_link/plugin.rs
1//! Plugin metadata types.
2//!
3//! A plugin is a WASM component that implements one [`Binding`]( crate::Binding )
4//! (its **plug**) and may depend on zero or more other [`Binding`]( crate::Binding )s
5//! (its **sockets**). The plug declares what the plugin exports; sockets declare what
6//! the plugin expects to import from other plugins.
7
8use std::collections::HashMap ;
9use wasmtime::{ Engine, Store };
10use wasmtime::component::{ Component, ResourceTable, Linker, Val };
11use futures::task::Spawn ;
12
13use crate::BindingAny ;
14use crate::plugin_instance::{ PluginInstanceAsync, PluginInstanceSync };
15use crate::Function ;
16use crate::Remap ;
17
18/// Trait for accessing a [`ResourceTable`] from the store's data type.
19///
20/// Resources that flow between plugins need to be wrapped to track ownership.
21/// This trait provides access to the table where those wrapped resources are stored.
22/// [`ResourceTable`] is part of the wasmtime component model; see the
23/// [wasmtime docs](https://docs.rs/wasmtime/latest/wasmtime/component/) for details.
24///
25/// # Example
26///
27/// ```
28/// use wasmtime::component::ResourceTable ;
29/// use wasm_link::PluginContext ;
30///
31/// struct MyPluginData {
32/// resource_table: ResourceTable,
33/// // ... other fields
34/// }
35///
36/// impl PluginContext for MyPluginData {
37/// fn resource_table( &mut self ) -> &mut ResourceTable {
38/// &mut self.resource_table
39/// }
40/// }
41/// ```
42pub trait PluginContext: Send {
43 /// Returns a mutable reference to a resource table.
44 fn resource_table( &mut self ) -> &mut ResourceTable ;
45}
46
47/// A WASM component bundled with its runtime context, ready for instantiation.
48///
49/// The component's exports (its **plug**) and imports (its **sockets**) are defined through
50/// the [`crate::Binding`], not by this struct.
51///
52/// The `context` is consumed during linking to become the wasmtime [`Store`]( wasmtime::Store )'s data.
53///
54/// # Type Parameters
55/// - `Ctx`: User context type that will be stored in the wasmtime [`Store`]( wasmtime::Store )
56///
57/// # Example
58///
59/// ```
60/// # use wasm_link::{ Plugin, PluginContext, ResourceTable, Component, Engine, Linker };
61/// # struct Ctx { resource_table: ResourceTable }
62/// # impl PluginContext for Ctx {
63/// # fn resource_table( &mut self ) -> &mut ResourceTable { &mut self.resource_table }
64/// # }
65/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
66/// let engine = Engine::default();
67/// let linker = Linker::new( &engine );
68///
69/// let plugin = Plugin::new(
70/// Component::new( &engine, "(component)" )?,
71/// Ctx { resource_table: ResourceTable::new() },
72/// ).instantiate( &engine, &linker )?;
73/// # let _ = plugin;
74/// # Ok(())
75/// # }
76/// ```
77#[must_use = "call .instantiate() or .link() to create a PluginInstanceSync"]
78pub struct Plugin<Ctx: 'static> {
79 /// Compiled WASM component
80 component: Component,
81 /// User context consumed at load time to become `Store<Ctx>`
82 context: Ctx,
83 /// Per-interface export name remaps for this plugin
84 interface_remaps: HashMap<String, Remap>,
85 /// Closure that determines fuel for each function call
86 #[allow( clippy::type_complexity )]
87 fuel_limiter: Option<Box<dyn FnMut( &mut Store<Ctx>, &str, &str, &Function ) -> u64 + Send>>,
88 /// Closure that determines epoch deadline for each function call
89 #[allow( clippy::type_complexity )]
90 epoch_limiter: Option<Box<dyn FnMut( &mut Store<Ctx>, &str, &str, &Function ) -> u64 + Send>>,
91 /// Closure that returns a mutable reference to the `ResourceLimiter` in the context
92 #[allow( clippy::type_complexity )]
93 memory_limiter: Option<Box<dyn (FnMut( &mut Ctx ) -> &mut dyn wasmtime::ResourceLimiter) + Send + Sync>>,
94}
95
96impl<Ctx> Plugin<Ctx>
97where
98 Ctx: PluginContext + 'static,
99{
100
101 /// Creates a new plugin declaration.
102 ///
103 /// Note that the plugin ID is not specified here - it's provided when constructing
104 /// the cardinality wrapper that holds this plugin. This is done to prevent duplicate ids.
105 pub fn new(
106 component: Component,
107 context: Ctx,
108 ) -> Self {
109 Self {
110 component,
111 context,
112 interface_remaps: HashMap::new(),
113 fuel_limiter: None,
114 epoch_limiter: None,
115 memory_limiter: None,
116 }
117 }
118
119 /// Sets a closure that determines the fuel limit for each function call.
120 ///
121 /// The closure receives the store, the interface path (e.g., `"my:package/api"`),
122 /// the function name, and the [`Function`] metadata. It returns the fuel to set.
123 ///
124 /// **Warning:** Fuel consumption must be enabled in the [`Engine`]( wasmtime::Engine )
125 /// via [`Config::consume_fuel`]( wasmtime::Config::consume_fuel ). If not enabled,
126 /// dispatch will fail with a [`RuntimeException`]( crate::DispatchError::RuntimeException )
127 /// at call time.
128 ///
129 /// ```
130 /// # use wasm_link::{ Plugin, PluginContext, ResourceTable, Component, Engine };
131 /// # struct Ctx { resource_table: ResourceTable }
132 /// # impl PluginContext for Ctx {
133 /// # fn resource_table( &mut self ) -> &mut ResourceTable { &mut self.resource_table }
134 /// # }
135 /// # fn example( component: Component ) {
136 /// let plugin = Plugin::new( component, Ctx { resource_table: ResourceTable::new() })
137 /// .with_fuel_limiter(| _store, _interface, _function, _metadata | 100_000 );
138 /// # }
139 /// ```
140 pub fn with_fuel_limiter( mut self, limiter: impl FnMut( &mut Store<Ctx>, &str, &str, &Function ) -> u64 + Send + 'static ) -> Self {
141 self.fuel_limiter = Some( Box::new( limiter ));
142 self
143 }
144
145 /// Sets a closure that determines the epoch deadline for each function call.
146 ///
147 /// The closure receives the store, the interface path (e.g., `"my:package/api"`),
148 /// the function name, and the [`Function`] metadata. It returns the epoch deadline
149 /// in ticks.
150 ///
151 /// **Warning:** Epoch interruption must be enabled in the [`Engine`]( wasmtime::Engine )
152 /// via [`Config::epoch_interruption`]( wasmtime::Config::epoch_interruption ). If not
153 /// enabled, the deadline is silently ignored.
154 ///
155 /// ```
156 /// # use wasm_link::{ Plugin, PluginContext, ResourceTable, Component, Engine };
157 /// # struct Ctx { resource_table: ResourceTable }
158 /// # impl PluginContext for Ctx {
159 /// # fn resource_table( &mut self ) -> &mut ResourceTable { &mut self.resource_table }
160 /// # }
161 /// # fn example( component: Component ) {
162 /// let plugin = Plugin::new( component, Ctx { resource_table: ResourceTable::new() })
163 /// .with_epoch_limiter(| _store, _interface, _function, _metadata | 5 );
164 /// # }
165 /// ```
166 pub fn with_epoch_limiter( mut self, limiter: impl FnMut( &mut Store<Ctx>, &str, &str, &Function ) -> u64 + Send + 'static ) -> Self {
167 self.epoch_limiter = Some( Box::new( limiter ));
168 self
169 }
170
171 /// Sets a closure that returns a mutable reference to a [`ResourceLimiter`]( wasmtime::ResourceLimiter )
172 /// embedded in the plugin context.
173 ///
174 /// The limiter is installed into the wasmtime [`Store`]( wasmtime::Store ) once at instantiation
175 /// and controls memory and table growth for the lifetime of the plugin.
176 ///
177 /// The [`ResourceLimiter`]( wasmtime::ResourceLimiter ) must be stored inside the context type `Ctx`
178 /// so that wasmtime can access it through a `&mut Ctx` reference.
179 ///
180 /// ```
181 /// # use wasm_link::{ Plugin, PluginContext, ResourceTable, Component, Engine };
182 /// # struct Ctx { resource_table: ResourceTable, limiter: MyLimiter }
183 /// # impl PluginContext for Ctx {
184 /// # fn resource_table( &mut self ) -> &mut ResourceTable { &mut self.resource_table }
185 /// # }
186 /// # struct MyLimiter;
187 /// # impl wasmtime::ResourceLimiter for MyLimiter {
188 /// # fn memory_growing( &mut self, _: usize, _: usize, _: Option<usize> ) -> wasmtime::Result<bool> { Ok( true ) }
189 /// # fn table_growing( &mut self, _: usize, _: usize, _: Option<usize> ) -> wasmtime::Result<bool> { Ok( true ) }
190 /// # }
191 /// # fn example( component: Component ) {
192 /// let plugin = Plugin::new( component, Ctx { resource_table: ResourceTable::new(), limiter: MyLimiter })
193 /// .with_memory_limiter(| ctx | &mut ctx.limiter );
194 /// # }
195 /// ```
196 pub fn with_memory_limiter(
197 mut self,
198 limiter: impl (FnMut( &mut Ctx ) -> &mut dyn wasmtime::ResourceLimiter) + Send + Sync + 'static,
199 ) -> Self {
200 self.memory_limiter = Some( Box::new( limiter ));
201 self
202 }
203
204 /// Sets interface export remaps for this plugin.
205 ///
206 /// Use this when a plugin implements the same interface types as its binding
207 /// but exports one or more interfaces or functions under different names.
208 ///
209 /// The outer map is a lookup table from requested interface name to [`Remap`].
210 /// Each [`Remap`] describes where that requested interface, and optionally
211 /// requested items inside it, are found in this plugin's exports.
212 ///
213 /// All remap tables use the same direction:
214 ///
215 /// ```text
216 /// requested name -> exported name
217 /// ```
218 ///
219 /// ```
220 /// # use std::collections::HashMap ;
221 /// # use wasm_link::{ Plugin, PluginContext, ResourceTable, Component, Engine, Remap };
222 /// # struct Ctx { resource_table: ResourceTable }
223 /// # impl PluginContext for Ctx {
224 /// # fn resource_table( &mut self ) -> &mut ResourceTable { &mut self.resource_table }
225 /// # }
226 /// # fn example( engine: &Engine ) -> Result<(), Box<dyn std::error::Error>> {
227 /// let plugin = Plugin::new(
228 /// Component::new( engine, "(component)" )?,
229 /// Ctx { resource_table: ResourceTable::new() },
230 /// ).remap_interfaces( HashMap::from([
231 /// ( "root".to_string(), Remap::found_as( "legacy-root" )),
232 /// ]));
233 /// # let _ = plugin ;
234 /// # Ok(())
235 /// # }
236 /// ```
237 pub fn remap_interfaces( mut self, interface_remaps: HashMap<String, Remap> ) -> Self {
238 self.interface_remaps = interface_remaps ;
239 self
240 }
241
242 /// Links this plugin with its socket bindings and instantiates it.
243 ///
244 /// Takes ownership of the `linker` because socket bindings are added to it. If you need
245 /// to reuse the same linker for multiple plugins, clone it before passing it in.
246 ///
247 /// # Type Parameters
248 /// - `PluginId`: Must implement `Into<Val>` so plugin IDs can be passed to WASM when
249 /// dispatching to multi-plugin sockets (the ID identifies which plugin produced each result).
250 ///
251 /// # Errors
252 /// Returns an error if linking or instantiation fails.
253 pub fn link<PluginId, Sockets>(
254 self,
255 engine: &Engine,
256 mut linker: Linker<Ctx>,
257 sockets: Sockets,
258 ) -> Result<PluginInstanceSync<Ctx>, wasmtime::Error>
259 where
260 PluginId: Eq + std::hash::Hash + Clone + std::fmt::Debug + Send + Sync + Into<Val> + 'static,
261 Sockets: IntoIterator,
262 Sockets::Item: Into<BindingAny<PluginId, Ctx>>,
263 {
264 sockets.into_iter()
265 .map( Into::into )
266 .try_for_each(| binding | binding.add_to_linker( &mut linker ))?;
267 Self::instantiate( self, engine, &linker )
268 }
269
270 /// Asynchronously links this plugin with its socket bindings and instantiates it.
271 ///
272 /// Use this variant when any socket may suspend or uses Component Model async types.
273 /// Every plugin in an asynchronously linked graph should be created with
274 /// [`instantiate_async`](Self::instantiate_async) or `link_async`.
275 /// Calls to the returned instance are submitted to `executor`, allowing a
276 /// thread pool to drive many independent plugin stores without reserving a
277 /// worker for each plugin.
278 ///
279 /// # Example
280 ///
281 /// ```
282 /// # use wasm_link::{ BindingAny, Component, Engine, Linker, Plugin, PluginContext, ResourceTable };
283 /// # struct Context { table: ResourceTable }
284 /// # impl PluginContext for Context { fn resource_table( &mut self ) -> &mut ResourceTable { &mut self.table } }
285 /// # fn main() -> Result<(), Box<dyn std::error::Error>> { futures::executor::block_on( async {
286 /// let engine = Engine::default();
287 /// let linker = Linker::new( &engine );
288 /// let executor = futures::executor::ThreadPool::new()?;
289 /// let instance = Plugin::new(
290 /// Component::new( &engine, "(component)" )?,
291 /// Context { table: ResourceTable::new() },
292 /// ).link_async(
293 /// &engine,
294 /// linker,
295 /// Vec::<BindingAny<String, Context, wasm_link::PluginInstanceAsync<Context>>>::new(),
296 /// executor,
297 /// ).await?;
298 /// # let _ = instance;
299 /// # Ok(()) }) }
300 /// ```
301 ///
302 /// # Errors
303 /// Returns an error if linking or instantiation fails.
304 pub async fn link_async<PluginId, Sockets, Executor>(
305 self,
306 engine: &Engine,
307 mut linker: Linker<Ctx>,
308 sockets: Sockets,
309 executor: Executor,
310 ) -> Result<PluginInstanceAsync<Ctx>, wasmtime::Error>
311 where
312 PluginId: Eq + std::hash::Hash + Clone + std::fmt::Debug + Send + Sync + Into<Val> + 'static,
313 Sockets: IntoIterator,
314 Sockets::Item: Into<BindingAny<PluginId, Ctx, PluginInstanceAsync<Ctx>>>,
315 Executor: Spawn + Send + Sync + 'static,
316 {
317 sockets.into_iter()
318 .map( Into::into )
319 .try_for_each(| binding | binding.add_to_linker_async( &mut linker ))?;
320 Self::instantiate_async( self, engine, &linker, executor ).await
321 }
322
323 /// A convenience alias for [`Plugin::link`] with 0 sockets
324 ///
325 /// # Errors
326 /// Returns an error if instantiation fails.
327 pub fn instantiate(
328 self,
329 engine: &Engine,
330 linker: &Linker<Ctx>
331 ) -> Result<PluginInstanceSync<Ctx>, wasmtime::Error> {
332 let mut store = Store::new( engine, self.context );
333 if let Some( limiter ) = self.memory_limiter { store.limiter( limiter ); }
334 let instance = linker.instantiate( &mut store, &self.component )?;
335 Ok( PluginInstanceSync::new_sync(
336 store,
337 instance,
338 self.interface_remaps,
339 self.fuel_limiter,
340 self.epoch_limiter,
341 ))
342 }
343
344 /// Asynchronously instantiates this plugin.
345 ///
346 /// This variant is required for WIT async functions, asynchronous host functions,
347 /// and plugins that will be used in a graph created with [`link_async`](Self::link_async).
348 /// Calls to the returned instance are submitted to `executor`. This keeps each
349 /// plugin's [`Store`](wasmtime::Store) isolated while allowing a thread pool to
350 /// drive many plugin stores without dedicating an idle thread to each one.
351 /// Wasmtime concurrency support, which is enabled by default, must not be disabled
352 /// on the `engine` used for asynchronous instances.
353 ///
354 /// # Example
355 ///
356 /// ```
357 /// # use wasm_link::{ Component, Engine, Linker, Plugin, PluginContext, ResourceTable };
358 /// # struct Context { table: ResourceTable }
359 /// # impl PluginContext for Context { fn resource_table( &mut self ) -> &mut ResourceTable { &mut self.table } }
360 /// # fn main() -> Result<(), Box<dyn std::error::Error>> { futures::executor::block_on( async {
361 /// let engine = Engine::default();
362 /// let linker = Linker::new( &engine );
363 /// let executor = futures::executor::ThreadPool::new()?;
364 /// let instance = Plugin::new(
365 /// Component::new( &engine, "(component)" )?,
366 /// Context { table: ResourceTable::new() },
367 /// ).instantiate_async( &engine, &linker, executor ).await?;
368 /// # let _ = instance;
369 /// # Ok(()) }) }
370 /// ```
371 ///
372 /// # Errors
373 /// Returns an error if instantiation fails.
374 pub async fn instantiate_async<Executor>(
375 self,
376 engine: &Engine,
377 linker: &Linker<Ctx>,
378 executor: Executor,
379 ) -> Result<PluginInstanceAsync<Ctx>, wasmtime::Error>
380 where
381 Executor: Spawn + Send + Sync + 'static,
382 {
383 let mut store = Store::new( engine, self.context );
384 if let Some( limiter ) = self.memory_limiter { store.limiter( limiter ); }
385 let instance = linker.instantiate_async( &mut store, &self.component ).await?;
386 Ok( PluginInstanceAsync::new(
387 store,
388 instance,
389 self.interface_remaps,
390 self.fuel_limiter,
391 self.epoch_limiter,
392 executor,
393 ))
394 }
395
396}
397
398impl<Ctx: std::fmt::Debug + 'static> std::fmt::Debug for Plugin<Ctx> {
399 fn fmt( &self, f: &mut std::fmt::Formatter<'_> ) -> std::fmt::Result {
400 f.debug_struct( "Plugin" )
401 .field( "component", &"<Component>" )
402 .field( "context", &self.context )
403 .field( "interface_remaps", &self.interface_remaps )
404 .field( "fuel_limiter", &self.fuel_limiter.as_ref().map(| _ | "<closure>" ))
405 .field( "epoch_limiter", &self.epoch_limiter.as_ref().map(| _ | "<closure>" ))
406 .field( "memory_limiter", &self.memory_limiter.as_ref().map(| _ | "<closure>" ))
407 .finish_non_exhaustive()
408 }
409}