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 /// Fuel assigned to the store before component instantiation
86 initial_fuel: Option<u64>,
87 /// Closure that determines fuel for each function call
88 #[allow( clippy::type_complexity )]
89 fuel_limiter: Option<Box<dyn FnMut( &mut Store<Ctx>, &str, &str, &Function ) -> u64 + Send>>,
90 /// Closure that determines epoch deadline for each function call
91 #[allow( clippy::type_complexity )]
92 epoch_limiter: Option<Box<dyn FnMut( &mut Store<Ctx>, &str, &str, &Function ) -> u64 + Send>>,
93 /// Closure that returns a mutable reference to the `ResourceLimiter` in the context
94 #[allow( clippy::type_complexity )]
95 memory_limiter: Option<Box<dyn (FnMut( &mut Ctx ) -> &mut dyn wasmtime::ResourceLimiter) + Send + Sync>>,
96}
97
98impl<Ctx> Plugin<Ctx>
99where
100 Ctx: PluginContext + 'static,
101{
102
103 /// Creates a new plugin declaration.
104 ///
105 /// Note that the plugin ID is not specified here - it's provided when constructing
106 /// the cardinality wrapper that holds this plugin. This is done to prevent duplicate ids.
107 pub fn new(
108 component: Component,
109 context: Ctx,
110 ) -> Self {
111 Self {
112 component,
113 context,
114 interface_remaps: HashMap::new(),
115 initial_fuel: None,
116 fuel_limiter: None,
117 epoch_limiter: None,
118 memory_limiter: None,
119 }
120 }
121
122 /// Sets the fuel available when component instantiation begins.
123 ///
124 /// Instantiation can execute WebAssembly startup code, including complex global,
125 /// element, table, and memory initializers and explicit start functions. Any fuel
126 /// left after instantiation remains available to subsequent calls. A
127 /// [`with_fuel_limiter`](Self::with_fuel_limiter) invocation may inspect or replace
128 /// that remainder before a call.
129 ///
130 /// **Warning:** Fuel consumption must be enabled in the [`Engine`]( wasmtime::Engine )
131 /// via [`Config::consume_fuel`]( wasmtime::Config::consume_fuel ). If not enabled,
132 /// instantiation will fail when the initial fuel is applied.
133 ///
134 /// ```
135 /// # use wasm_link::{ Plugin, PluginContext, ResourceTable, Component };
136 /// # struct Ctx { resource_table: ResourceTable }
137 /// # impl PluginContext for Ctx {
138 /// # fn resource_table( &mut self ) -> &mut ResourceTable { &mut self.resource_table }
139 /// # }
140 /// # fn example( component: Component ) {
141 /// let plugin = Plugin::new( component, Ctx { resource_table: ResourceTable::new() })
142 /// .with_initial_fuel( 100_000 );
143 /// # let _ = plugin;
144 /// # }
145 /// ```
146 pub fn with_initial_fuel( mut self, fuel: u64 ) -> Self {
147 self.initial_fuel = Some( fuel );
148 self
149 }
150
151 /// Sets a closure that determines the fuel limit for each function call.
152 ///
153 /// The closure receives the store, the interface path (e.g., `"my:package/api"`),
154 /// the function name, and the [`Function`] metadata. It returns the fuel to set.
155 ///
156 /// **Warning:** Fuel consumption must be enabled in the [`Engine`]( wasmtime::Engine )
157 /// via [`Config::consume_fuel`]( wasmtime::Config::consume_fuel ). If not enabled,
158 /// dispatch will fail with a [`RuntimeException`]( crate::DispatchError::RuntimeException )
159 /// at call time.
160 ///
161 /// ```
162 /// # use wasm_link::{ Plugin, PluginContext, ResourceTable, Component, Engine };
163 /// # struct Ctx { resource_table: ResourceTable }
164 /// # impl PluginContext for Ctx {
165 /// # fn resource_table( &mut self ) -> &mut ResourceTable { &mut self.resource_table }
166 /// # }
167 /// # fn example( component: Component ) {
168 /// let plugin = Plugin::new( component, Ctx { resource_table: ResourceTable::new() })
169 /// .with_fuel_limiter(| _store, _interface, _function, _metadata | 100_000 );
170 /// # }
171 /// ```
172 pub fn with_fuel_limiter( mut self, limiter: impl FnMut( &mut Store<Ctx>, &str, &str, &Function ) -> u64 + Send + 'static ) -> Self {
173 self.fuel_limiter = Some( Box::new( limiter ));
174 self
175 }
176
177 /// Sets a closure that determines the epoch deadline for each function call.
178 ///
179 /// The closure receives the store, the interface path (e.g., `"my:package/api"`),
180 /// the function name, and the [`Function`] metadata. It returns the epoch deadline
181 /// in ticks.
182 ///
183 /// **Warning:** Epoch interruption must be enabled in the [`Engine`]( wasmtime::Engine )
184 /// via [`Config::epoch_interruption`]( wasmtime::Config::epoch_interruption ). If not
185 /// enabled, the deadline is silently ignored.
186 ///
187 /// ```
188 /// # use wasm_link::{ Plugin, PluginContext, ResourceTable, Component, Engine };
189 /// # struct Ctx { resource_table: ResourceTable }
190 /// # impl PluginContext for Ctx {
191 /// # fn resource_table( &mut self ) -> &mut ResourceTable { &mut self.resource_table }
192 /// # }
193 /// # fn example( component: Component ) {
194 /// let plugin = Plugin::new( component, Ctx { resource_table: ResourceTable::new() })
195 /// .with_epoch_limiter(| _store, _interface, _function, _metadata | 5 );
196 /// # }
197 /// ```
198 pub fn with_epoch_limiter( mut self, limiter: impl FnMut( &mut Store<Ctx>, &str, &str, &Function ) -> u64 + Send + 'static ) -> Self {
199 self.epoch_limiter = Some( Box::new( limiter ));
200 self
201 }
202
203 /// Sets a closure that returns a mutable reference to a [`ResourceLimiter`]( wasmtime::ResourceLimiter )
204 /// embedded in the plugin context.
205 ///
206 /// The limiter is installed into the wasmtime [`Store`]( wasmtime::Store ) once at instantiation
207 /// and controls memory and table growth for the lifetime of the plugin.
208 ///
209 /// The [`ResourceLimiter`]( wasmtime::ResourceLimiter ) must be stored inside the context type `Ctx`
210 /// so that wasmtime can access it through a `&mut Ctx` reference.
211 ///
212 /// ```
213 /// # use wasm_link::{ Plugin, PluginContext, ResourceTable, Component, Engine };
214 /// # struct Ctx { resource_table: ResourceTable, limiter: MyLimiter }
215 /// # impl PluginContext for Ctx {
216 /// # fn resource_table( &mut self ) -> &mut ResourceTable { &mut self.resource_table }
217 /// # }
218 /// # struct MyLimiter;
219 /// # impl wasmtime::ResourceLimiter for MyLimiter {
220 /// # fn memory_growing( &mut self, _: usize, _: usize, _: Option<usize> ) -> wasmtime::Result<bool> { Ok( true ) }
221 /// # fn table_growing( &mut self, _: usize, _: usize, _: Option<usize> ) -> wasmtime::Result<bool> { Ok( true ) }
222 /// # }
223 /// # fn example( component: Component ) {
224 /// let plugin = Plugin::new( component, Ctx { resource_table: ResourceTable::new(), limiter: MyLimiter })
225 /// .with_memory_limiter(| ctx | &mut ctx.limiter );
226 /// # }
227 /// ```
228 pub fn with_memory_limiter(
229 mut self,
230 limiter: impl (FnMut( &mut Ctx ) -> &mut dyn wasmtime::ResourceLimiter) + Send + Sync + 'static,
231 ) -> Self {
232 self.memory_limiter = Some( Box::new( limiter ));
233 self
234 }
235
236 /// Sets interface export remaps for this plugin.
237 ///
238 /// Use this when a plugin implements the same interface types as its binding
239 /// but exports one or more interfaces or functions under different names.
240 ///
241 /// The outer map is a lookup table from requested interface name to [`Remap`].
242 /// Each [`Remap`] describes where that requested interface, and optionally
243 /// requested items inside it, are found in this plugin's exports.
244 ///
245 /// All remap tables use the same direction:
246 ///
247 /// ```text
248 /// requested name -> exported name
249 /// ```
250 ///
251 /// ```
252 /// # use std::collections::HashMap ;
253 /// # use wasm_link::{ Plugin, PluginContext, ResourceTable, Component, Engine, Remap };
254 /// # struct Ctx { resource_table: ResourceTable }
255 /// # impl PluginContext for Ctx {
256 /// # fn resource_table( &mut self ) -> &mut ResourceTable { &mut self.resource_table }
257 /// # }
258 /// # fn example( engine: &Engine ) -> Result<(), Box<dyn std::error::Error>> {
259 /// let plugin = Plugin::new(
260 /// Component::new( engine, "(component)" )?,
261 /// Ctx { resource_table: ResourceTable::new() },
262 /// ).remap_interfaces( HashMap::from([
263 /// ( "root".to_string(), Remap::found_as( "legacy-root" )),
264 /// ]));
265 /// # let _ = plugin ;
266 /// # Ok(())
267 /// # }
268 /// ```
269 pub fn remap_interfaces( mut self, interface_remaps: HashMap<String, Remap> ) -> Self {
270 self.interface_remaps = interface_remaps ;
271 self
272 }
273
274 /// Links this plugin with its socket bindings and instantiates it.
275 ///
276 /// Takes ownership of the `linker` because socket bindings are added to it. If you need
277 /// to reuse the same linker for multiple plugins, clone it before passing it in.
278 ///
279 /// # Type Parameters
280 /// - `PluginId`: Must implement `Into<Val>` so plugin IDs can be passed to WASM when
281 /// dispatching to multi-plugin sockets (the ID identifies which plugin produced each result).
282 ///
283 /// # Errors
284 /// Returns an error if linking or instantiation fails.
285 pub fn link<PluginId, Sockets>(
286 self,
287 engine: &Engine,
288 mut linker: Linker<Ctx>,
289 sockets: Sockets,
290 ) -> Result<PluginInstanceSync<Ctx>, wasmtime::Error>
291 where
292 PluginId: Eq + std::hash::Hash + Clone + std::fmt::Debug + Send + Sync + Into<Val> + 'static,
293 Sockets: IntoIterator,
294 Sockets::Item: Into<BindingAny<PluginId, Ctx>>,
295 {
296 sockets.into_iter()
297 .map( Into::into )
298 .try_for_each(| binding | binding.add_to_linker( &mut linker ))?;
299 Self::instantiate( self, engine, &linker )
300 }
301
302 /// Asynchronously links this plugin with its socket bindings and instantiates it.
303 ///
304 /// Use this variant when any socket may suspend or uses Component Model async types.
305 /// Every plugin in an asynchronously linked graph should be created with
306 /// [`instantiate_async`](Self::instantiate_async) or `link_async`.
307 /// Calls to the returned instance are submitted to `executor`, allowing a
308 /// thread pool to drive many independent plugin stores without reserving a
309 /// worker for each plugin.
310 ///
311 /// # Example
312 ///
313 /// ```
314 /// # use wasm_link::{ BindingAny, Component, Engine, Linker, Plugin, PluginContext, ResourceTable };
315 /// # struct Context { table: ResourceTable }
316 /// # impl PluginContext for Context { fn resource_table( &mut self ) -> &mut ResourceTable { &mut self.table } }
317 /// # fn main() -> Result<(), Box<dyn std::error::Error>> { futures::executor::block_on( async {
318 /// let engine = Engine::default();
319 /// let linker = Linker::new( &engine );
320 /// let executor = futures::executor::ThreadPool::new()?;
321 /// let instance = Plugin::new(
322 /// Component::new( &engine, "(component)" )?,
323 /// Context { table: ResourceTable::new() },
324 /// ).link_async(
325 /// &engine,
326 /// linker,
327 /// Vec::<BindingAny<String, Context, wasm_link::PluginInstanceAsync<Context>>>::new(),
328 /// executor,
329 /// ).await?;
330 /// # let _ = instance;
331 /// # Ok(()) }) }
332 /// ```
333 ///
334 /// # Errors
335 /// Returns an error if linking or instantiation fails.
336 pub async fn link_async<PluginId, Sockets, Executor>(
337 self,
338 engine: &Engine,
339 mut linker: Linker<Ctx>,
340 sockets: Sockets,
341 executor: Executor,
342 ) -> Result<PluginInstanceAsync<Ctx>, wasmtime::Error>
343 where
344 PluginId: Eq + std::hash::Hash + Clone + std::fmt::Debug + Send + Sync + Into<Val> + 'static,
345 Sockets: IntoIterator,
346 Sockets::Item: Into<BindingAny<PluginId, Ctx, PluginInstanceAsync<Ctx>>>,
347 Executor: Spawn + Send + Sync + 'static,
348 {
349 sockets.into_iter()
350 .map( Into::into )
351 .try_for_each(| binding | binding.add_to_linker_async( &mut linker ))?;
352 Self::instantiate_async( self, engine, &linker, executor ).await
353 }
354
355 /// A convenience alias for [`Plugin::link`] with 0 sockets
356 ///
357 /// # Errors
358 /// Returns an error if instantiation fails.
359 pub fn instantiate(
360 self,
361 engine: &Engine,
362 linker: &Linker<Ctx>
363 ) -> Result<PluginInstanceSync<Ctx>, wasmtime::Error> {
364 let mut store = Store::new( engine, self.context );
365 if let Some( fuel ) = self.initial_fuel { store.set_fuel( fuel )?; }
366 if let Some( limiter ) = self.memory_limiter { store.limiter( limiter ); }
367 let instance = linker.instantiate( &mut store, &self.component )?;
368 Ok( PluginInstanceSync::new_sync(
369 store,
370 instance,
371 self.interface_remaps,
372 self.fuel_limiter,
373 self.epoch_limiter,
374 ))
375 }
376
377 /// Asynchronously instantiates this plugin.
378 ///
379 /// This variant is required for WIT async functions, asynchronous host functions,
380 /// and plugins that will be used in a graph created with [`link_async`](Self::link_async).
381 /// Calls to the returned instance are submitted to `executor`. This keeps each
382 /// plugin's [`Store`](wasmtime::Store) isolated while allowing a thread pool to
383 /// drive many plugin stores without dedicating an idle thread to each one.
384 /// Wasmtime concurrency support, which is enabled by default, must not be disabled
385 /// on the `engine` used for asynchronous instances.
386 ///
387 /// # Example
388 ///
389 /// ```
390 /// # use wasm_link::{ Component, Engine, Linker, Plugin, PluginContext, ResourceTable };
391 /// # struct Context { table: ResourceTable }
392 /// # impl PluginContext for Context { fn resource_table( &mut self ) -> &mut ResourceTable { &mut self.table } }
393 /// # fn main() -> Result<(), Box<dyn std::error::Error>> { futures::executor::block_on( async {
394 /// let engine = Engine::default();
395 /// let linker = Linker::new( &engine );
396 /// let executor = futures::executor::ThreadPool::new()?;
397 /// let instance = Plugin::new(
398 /// Component::new( &engine, "(component)" )?,
399 /// Context { table: ResourceTable::new() },
400 /// ).instantiate_async( &engine, &linker, executor ).await?;
401 /// # let _ = instance;
402 /// # Ok(()) }) }
403 /// ```
404 ///
405 /// # Errors
406 /// Returns an error if instantiation fails.
407 pub async fn instantiate_async<Executor>(
408 self,
409 engine: &Engine,
410 linker: &Linker<Ctx>,
411 executor: Executor,
412 ) -> Result<PluginInstanceAsync<Ctx>, wasmtime::Error>
413 where
414 Executor: Spawn + Send + Sync + 'static,
415 {
416 let mut store = Store::new( engine, self.context );
417 if let Some( fuel ) = self.initial_fuel { store.set_fuel( fuel )?; }
418 if let Some( limiter ) = self.memory_limiter { store.limiter( limiter ); }
419 let instance = linker.instantiate_async( &mut store, &self.component ).await?;
420 Ok( PluginInstanceAsync::new(
421 store,
422 instance,
423 self.interface_remaps,
424 self.fuel_limiter,
425 self.epoch_limiter,
426 executor,
427 ))
428 }
429
430}
431
432impl<Ctx: std::fmt::Debug + 'static> std::fmt::Debug for Plugin<Ctx> {
433 fn fmt( &self, f: &mut std::fmt::Formatter<'_> ) -> std::fmt::Result {
434 f.debug_struct( "Plugin" )
435 .field( "component", &"<Component>" )
436 .field( "context", &self.context )
437 .field( "interface_remaps", &self.interface_remaps )
438 .field( "initial_fuel", &self.initial_fuel )
439 .field( "fuel_limiter", &self.fuel_limiter.as_ref().map(| _ | "<closure>" ))
440 .field( "epoch_limiter", &self.epoch_limiter.as_ref().map(| _ | "<closure>" ))
441 .field( "memory_limiter", &self.memory_limiter.as_ref().map(| _ | "<closure>" ))
442 .finish_non_exhaustive()
443 }
444}