Skip to main content

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}