1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
//! A WebAssembly plugin runtime for building modular applications.
//!
//! Plugins are small, single-purpose WASM components that connect through abstract
//! bindings. Each plugin declares a **plug** (the binding it implements) and
//! zero or more **sockets** (bindings it depends on). `wasm_link` links these
//! into a directed acyclic graph (DAG) and handles cross-plugin dispatch.
//!
//! # Core Concepts
//!
//! - [`Binding`]: An abstract contract declaring what an implementer exports and what a
//! consumer may import. Contains a package name, a set of interfaces, and plugged-in
//! plugin instances.
//!
//! - [`Interface`]: A single WIT interface with functions and resources. Note that
//! interfaces don't have a name field; their names are provided as keys of a `HashMap`
//! when constructing a [`Binding`]. This prevents duplicate interface names.
//!
//! - [`Plugin`]: A struct containing a wasm component and the runtime context made available
//! to host exports; their ids are provided as keys of a `HashMap` when constructing a
//! [`Binding`]. This prevents duplicate ids.
//!
//! - [`PluginInstance`]: An instantiated plugin with its store and instance, ready for dispatch.
//!
//! - **Plug**: A plugin's declaration that it implements a [`Binding`].
//!
//! - **Socket**: A plugin's declaration that it depends on a [`Binding`]. Cardinality is
//! expressed with wrapper types in [`crate::cardinality`]:
//! - [`cardinality::ExactlyOne`]`( Id, T )` - exactly one plugin, guaranteed present
//! - [`cardinality::AtMostOne`]`( Option<( Id, T )> )` - zero or one plugin
//! - [`cardinality::AtLeastOne`]`( nonempty_collections::NEMap<Id, T> )` - one or more plugins
//! - [`cardinality::Any`]`( HashMap<Id, T> )` - zero or more plugins
//!
//! # Re-exports
//!
//! `wasm_link` re-exports a small set of types from `wasmtime` for convenience
//! (`Engine`, `Component`, `Linker`, `ResourceTable`, `Val`). These types are
//! defined by wasmtime; see the [wasmtime docs](https://docs.rs/wasmtime/latest/wasmtime/)
//! for details.
//!
//! # Example
//!
//! ```
//! use std::collections::{ HashMap, HashSet };
//! use wasm_link::{
//! Binding, Interface, Function, FunctionKind, ReturnKind,
//! Plugin, PluginContext, Engine, Component, Linker, ResourceTable, Val,
//! };
//! use wasm_link::cardinality::ExactlyOne ;
//!
//! // First, declare a plugin context, the data stored inside wasmtime `Store<T>`.
//! // It must contain a resource table to implement `PluginContext` which is needed
//! // for ownership tracking of wasm component model resources.
//! struct Context { resource_table: ResourceTable }
//!
//! impl PluginContext for Context {
//! fn resource_table( &mut self ) -> &mut ResourceTable {
//! &mut self.resource_table
//! }
//! }
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! // You create your own engine. This allows you to define your config but note that
//! // not all options are compatible. As a general rule of thumb, if an option changes
//! // the way you interact with wasm, it is likely not compatible since this is managed
//! // by `wasm_link` directly. If the option makes sense, it will likely be supported
//! // in the future through wasm_link options.
//! let engine = Engine::default();
//!
//! // Similarly you may create your own linker, which you can add any exports into.
//! // Such exports will be available to all the plugins. It is your responsibility to
//! // make sure these don't conflict with re-exports of plugins that some other plugin
//! // depends on as these too have to be added to the same linker.
//! let linker = Linker::new( &engine );
//!
//! // Build the DAG bottom-up: start with plugins that have no dependencies.
//! // Note that for plugins that don't require linking, you only need to pass in
//! // a reference to a linker. For plugins that have dependencies, the linker is mutated.
//! // Plugin IDs are specified in the cardinality wrapper to prevent duplicate ids.
//! let leaf = Plugin::new(
//! Component::new( &engine, "(component)" )?,
//! Context { resource_table: ResourceTable::new() },
//! ).instantiate( &engine, &linker )?;
//!
//! // Bindings expose a plugin's exports to other plugins.
//! // Wrapper sets cardinality: ExactlyOne, AtMostOne (0-1), AtLeastOne (1+), Any (0+).
//! let leaf_binding = Binding::new(
//! "empty:package",
//! HashMap::new(),
//! ExactlyOne( "leaf".to_string(), leaf ),
//! );
//!
//! // `link()` wires up dependencies - this plugin can now import from leaf_binding.
//! let root = Plugin::new(
//! Component::new( &engine, r#"(component
//! (core module $m (func (export "f") (result i32) i32.const 42))
//! (core instance $i (instantiate $m))
//! (func $f (export "get-value") (result u32) (canon lift (core func $i "f")))
//! (instance $inst (export "get-value" (func $f)))
//! (export "my:package/example" (instance $inst))
//! )"# )?,
//! Context { resource_table: ResourceTable::new() },
//! ).link( &engine, linker, vec![ leaf_binding ])?;
//!
//! // Interface tells `wasm_link` which functions exist and how to handle returns.
//! let root_binding = Binding::new(
//! "my:package",
//! HashMap::from([( "example".to_string(), Interface::new(
//! HashMap::from([( "get-value".into(), Function::new(
//! FunctionKind::Freestanding, ReturnKind::MayContainResources,
//! ))]),
//! HashSet::new(),
//! ))]),
//! ExactlyOne( "root".to_string(), root ),
//! );
//!
//! // Now you can call into the plugin graph from the host.
//! let result = root_binding.dispatch( "example", "get-value", &[ /* args */ ] )?;
//! match result {
//! ExactlyOne( _id, Ok( Val::U32( n ))) => assert_eq!( n, 42 ),
//! ExactlyOne( _id, Ok( _ )) => panic!( "unexpected response" ),
//! ExactlyOne( _id, Err( err )) => panic!( "dispatch error: {}", err ),
//! }
//! # Ok(())
//! # }
//! ```
//!
//! # Shared Dependencies
//!
//! Sometimes multiple plugins need to depend on the same binding. Since `Binding`
//! is a handle type, cloning it creates another reference to the same underlying
//! binding rather than duplicating it.
//!
//! ```
//! # use std::collections::HashMap ;
//! # use wasm_link::{ Binding, Plugin, PluginContext, Engine, Component, Linker, ResourceTable };
//! # use wasm_link::cardinality::ExactlyOne ;
//! # struct Context { resource_table: ResourceTable }
//! # impl PluginContext for Context {
//! # fn resource_table( &mut self ) -> &mut ResourceTable { &mut self.resource_table }
//! # }
//! # impl Context {
//! # pub fn new() -> Self { Self { resource_table: ResourceTable::new() } }
//! # }
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! # let engine = Engine::default();
//! # let linker = Linker::new( &engine );
//! let plugin_d = Plugin::new( Component::new( &engine, "(component)" )?, Context::new())
//! .instantiate( &engine, &linker )?;
//! let binding_d = Binding::new( "d:pkg", HashMap::new(), ExactlyOne( "D".to_string(), plugin_d ));
//!
//! // Both B and C import from D. Clone the binding handle so both can reference it.
//! let plugin_b = Plugin::new( Component::new( &engine, "(component)" )?, Context::new())
//! .link( &engine, linker.clone(), vec![ binding_d.clone() ])?;
//! let plugin_c = Plugin::new( Component::new( &engine, "(component)" )?, Context::new())
//! .link( &engine, linker.clone(), vec![ binding_d ])?;
//!
//! let binding_b = Binding::new( "b:pkg", HashMap::new(), ExactlyOne( "B".to_string(), plugin_b ));
//! let binding_c = Binding::new( "c:pkg", HashMap::new(), ExactlyOne( "C".to_string(), plugin_c ));
//!
//! let plugin_a = Plugin::new( Component::new( &engine, "(component)" )?, Context::new())
//! .link( &engine, linker, vec![ binding_b, binding_c ])?;
//! # let _ = plugin_a ;
//! # Ok(())
//! # }
//! ```
//!
//! # Multiple Plugins Per Binding
//!
//! A single binding can have multiple plugin implementations. Use [`cardinality::AtLeastOne`]
//! when at least one implementation is required, or [`cardinality::Any`] when zero is acceptable.
//! When you dispatch to such a binding, you get results from all plugins.
//!
//! ```
//! # use std::collections::{ HashMap, HashSet };
//! # use wasm_link::{
//! # Binding, Interface, Function, FunctionKind, ReturnKind, Plugin, PluginContext,
//! # Engine, Component, Linker, ResourceTable, Val,
//! # };
//! # use wasm_link::cardinality::Any ;
//! # struct Context { resource_table: ResourceTable }
//! # impl Context {
//! # pub fn new() -> Self { Self { resource_table: ResourceTable::new() } }
//! # }
//! # impl PluginContext for Context {
//! # fn resource_table( &mut self ) -> &mut ResourceTable { &mut self.resource_table }
//! # }
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! # let engine = Engine::default();
//! # let linker = Linker::new( &engine );
//! // Plugin IDs are specified through the HashMap keys for Any.
//! let plugin1 = Plugin::new( Component::new( &engine, r#"(component
//! (core module $m (func (export "f") (result i32) i32.const 1))
//! (core instance $i (instantiate $m))
//! (func $f (result u32) (canon lift (core func $i "f")))
//! (instance $inst (export "get-value" (func $f)))
//! (export "pkg:interface/root" (instance $inst))
//! )"# )?, Context::new()).instantiate( &engine, &linker )?;
//!
//! let plugin2 = Plugin::new( Component::new( &engine, r#"(component
//! (core module $m (func (export "f") (result i32) i32.const 2))
//! (core instance $i (instantiate $m))
//! (func $f (result u32) (canon lift (core func $i "f")))
//! (instance $inst (export "get-value" (func $f)))
//! (export "pkg:interface/root" (instance $inst))
//! )"# )?, Context::new()).instantiate( &engine, &linker )?;
//!
//! let binding = Binding::new(
//! "pkg:interface",
//! HashMap::from([( "root".to_string(), Interface::new(
//! HashMap::from([( "get-value".into(), Function::new(
//! FunctionKind::Freestanding,
//! ReturnKind::MayContainResources,
//! ))]),
//! HashSet::new(),
//! ))]),
//! Any( HashMap::from([
//! ( "p1".to_string(), plugin1 ),
//! ( "p2".to_string(), plugin2 ),
//! ])),
//! );
//!
//! // Dispatch calls all plugins; the result wrapper matches what you passed in.
//! let Any( map ) = binding.dispatch( "root", "get-value", &[] )?;
//! assert_eq!( map.len(), 2 );
//! assert!( matches!( map.get( "p1" ), Some( Ok( Val::U32( 1 )))));
//! assert!( matches!( map.get( "p2" ), Some( Ok( Val::U32( 2 )))));
//! # Ok(())
//! # }
//! ```
//!
//! # Resource Limits
//!
//! Plugins may run untrusted code. `wasm_link` exposes three mechanisms to control
//! resource usage:
//!
//! - **Fuel** counts WebAssembly instructions. When fuel runs out, execution traps.
//! Enable with [`Config::consume_fuel`]( wasmtime::Config::consume_fuel ).
//! Set per-call via [`Plugin::with_fuel_limiter`].
//!
//! - **Epoch deadline** counts external timer ticks. When the deadline is reached,
//! execution traps. Enable with [`Config::epoch_interruption`]( wasmtime::Config::epoch_interruption ).
//! Set per-call via [`Plugin::with_epoch_limiter`].
//!
//! - **Memory** limits linear memory and table growth via wasmtime's
//! [`ResourceLimiter`]( wasmtime::ResourceLimiter ). No engine configuration required.
//! Set once at instantiation via [`Plugin::with_memory_limiter`].
//!
//! ## Fuel and Epoch Limits
//!
//! Fuel and epoch limits are set per-plugin via closures that receive the store,
//! WIT interface path, function name, and function metadata. This gives you full
//! control over the limit per call.
//!
//! ```
//! # use std::collections::{ HashMap, HashSet };
//! # use wasm_link::{ Binding, Interface, Function, FunctionKind, ReturnKind, Plugin, PluginContext, Component, Linker, ResourceTable };
//! # use wasm_link::cardinality::ExactlyOne ;
//! # use wasmtime::{ Config, Engine };
//! # struct Context { resource_table: ResourceTable }
//! # impl Context { fn new() -> Self { Self { resource_table: ResourceTable::new() }}}
//! # impl PluginContext for Context {
//! # fn resource_table( &mut self ) -> &mut ResourceTable { &mut self.resource_table }
//! # }
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! // Enable fuel consumption in the engine
//! let mut config = Config::new();
//! config.consume_fuel( true );
//! let engine = Engine::new( &config )?;
//! let linker = Linker::new( &engine );
//!
//! # let component = Component::new( &engine, "(component)" )?;
//! // Give this plugin a flat fuel budget per call
//! let plugin = Plugin::new( component, Context::new() )
//! .with_fuel_limiter(| _store, _interface, _function, _metadata | 100_000 )
//! .instantiate( &engine, &linker )?;
//!
//! let binding = Binding::<String, _>::new(
//! "my:pkg",
//! HashMap::from([( "api".into(), Interface::new(
//! HashMap::from([
//! ( "cheap-fn".into(), Function::new( FunctionKind::Freestanding, ReturnKind::Void )),
//! ( "expensive-fn".into(), Function::new( FunctionKind::Freestanding, ReturnKind::Void )),
//! ]),
//! HashSet::new(),
//! ))]),
//! ExactlyOne( "plugin".into(), plugin ),
//! );
//! # Ok(())
//! # }
//! ```
//! ## Important Notes
//!
//! **Engine configuration is required.** Fuel and epoch deadline limits only work when enabled
//! in the [`Engine`] configuration. Memory limits require no engine configuration.
//! For more information, see the [wasmtime docs](https://docs.rs/wasmtime/latest/wasmtime/).
//!
//! **Fuel and epoch deadlines are independent.** A function can have both a fuel limit and an
//! epoch deadline. They are applied separately; whichever is exhausted first causes
//! a trap.
//!
//! **Engine enabled but no limiter set.** If you enable fuel/epoch deadlines in the [`Engine`]
//! but don't set a limiter on the [`Plugin`], the behavior mimics the wasmtime default.
//! - *Fuel*: A fresh [`Store`]( wasmtime::Store ) starts with 0 fuel, so the first
//! instruction immediately traps. This is likely not what you want.
//! - *Epoch deadlines*: No deadline is set, so execution runs indefinitely regardless of epoch
//! ticks.
//!
//! ## Memory Limits
//!
//! Memory limits are implemented via wasmtime's [`ResourceLimiter`]( wasmtime::ResourceLimiter ),
//! which you implement and store inside your plugin context. The limiter is installed
//! once at instantiation and controls memory and table growth for the plugin's lifetime.
//! No engine configuration is required.
//!
//! ```
//! # use wasm_link::{ Plugin, PluginContext, ResourceTable, Component, Engine, Linker };
//! # use wasmtime::ResourceLimiter;
//! struct Ctx {
//! resource_table: ResourceTable,
//! limiter: MemoryLimiter,
//! }
//! impl PluginContext for Ctx {
//! fn resource_table( &mut self ) -> &mut ResourceTable { &mut self.resource_table }
//! }
//!
//! struct MemoryLimiter { max_bytes: usize }
//! impl ResourceLimiter for MemoryLimiter {
//! fn memory_growing( &mut self, _current: usize, desired: usize, _max: Option<usize> ) -> wasmtime::Result<bool> {
//! Ok( desired <= self.max_bytes )
//! }
//! fn table_growing( &mut self, _current: usize, _desired: usize, _max: Option<usize> ) -> wasmtime::Result<bool> {
//! Ok( true )
//! }
//! }
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let engine = Engine::default();
//! let linker = Linker::new( &engine );
//! # let component = Component::new( &engine, "(component)" )?;
//! let plugin = Plugin::new( component, Ctx {
//! resource_table: ResourceTable::new(),
//! limiter: MemoryLimiter { max_bytes: 10 * 1024 * 1024 }, // 10 MiB
//! }).with_memory_limiter(| ctx | &mut ctx.limiter )
//! .instantiate( &engine, &linker )?;
//! # let _ = plugin;
//! # Ok(())
//! # }
//! ```
pub use Engine ;
pub use ;
pub use ;
pub use Binding ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use BindingAny ;
pub use ;