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	/// 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}