xref: /wasmtime-44.0.1/docs/wasip2-plugins.md (revision 63f094df)
1# Calculator with WebAssembly Plugins
2
3This example demonstrates how to embed Wasmtime
4to create an application that uses plugins.
5The plugins are WebAssembly components.
6
7You can [browse this source code online](https://github.com/bytecodealliance/wasmtime/blob/main/examples/wasip2-plugins)
8and clone the wasmtime repository to run this example locally.
9
10This application is a simplified version of the application presented
11in Sy Brand's blog post
12["Building Native Plugin Systems with WebAssembly Components "](https://tartanllama.xyz/posts/wasm-plugins/).
13Consult that blog post for a more complex example of embedding Wasmtime
14and using plugins.
15
16## The calculator
17
18The calculator being implemented is very simple;
19it takes an expression represented in prefix form, without parentheses,
20on the command line. For example:
21
22```
23target/release/calculator --plugins plugins/ add 1 2
24```
25
26or
27
28```
29target/release/calculator --plugins plugins/ subtract -1 -2
30```
31
32The set of operations available is defined by the set of plugins present in
33the `plugins/` directory.
34
35Each plugin is a component that supports two operations:
36* `get_plugin_name`: Returns the name of the arithmetic operation
37  that this plugin implements.
38* `evaluate`: Takes two signed integer arguments and returns the result
39  of evaluating the operation on the arguments.
40
41Two example plugins are included: an `add` plugin implemented in C,
42and a `subtract` plugin implemented in JavaScript.
43Running `cargo build --release` will generate the plugins:
44`c-plugin/add.wasm` and `js-plugin/subtract.wasm`.
45To run the code, you should copy both of these files into the
46`plugins/` directory that you provide with the `--plugins` option.
47
48> To build the plugins, you must install `wit-bindgen` and the WASI SDK
49> (for building the C plugin) and `jco` (for building the JavaScript
50> plugin). For instructions, see [the C/C++ section](https://component-model.bytecodealliance.org/language-support/c.html)
51> and [the JavaScript section](https://component-model.bytecodealliance.org/language-support/javascript.html)
52> of the [Component Model documentation](https://component-model.bytecodealliance.org/).
53
54There are no nested expressions.
55
56## WIT bindings
57
58To define the interface for the plugins, we have to create a `.wit` file.
59The contents of this file are:
60
61```wit
62package docs:calculator;
63
64interface host {}
65
66world plugin {
67    import host;
68
69    export get-plugin-name: func() -> string;
70    export evaluate: func(x: s32, y: s32) -> s32;
71}
72```
73
74The WIT file defines a world that the plugin must implement,
75as well as any imports it can expect its host to provide.
76In this case, the `host` interface is empty, indicating that
77there is no functionality in the host that a plugin can call.
78
79The world has two exports, indicating that the plugin must implement
80two functions: a `get-plugin-name` function that returns the name
81of the operation that this plugin implements, and an `evaluate` function
82that does computation specific to this plugin.
83
84In the calculator code, we write:
85
86```wit
87bindgen!("plugin");
88```
89
90which uses a macro provided by Wasmtime that automatically runs
91the `wit-bindgen` tool to generate bindings for the world.
92
93## `CalculatorState`
94
95We use the `CalculatorState` type to represent the global state of the program,
96which in this case is just a mapping from strings that represent operation names
97to `PluginDesc`s. A `PluginDesc` represents the information needed in order
98to execute a plugin given arguments.
99
100## Loading plugins
101
102The application takes a directory as a command-line argument,
103which is expected to contain plugins (as .wasm files).
104All plugins in the directory are loaded eagerly.
105
106The `load_plugins()` function starts by calling the Wasmtime library's
107`Engine::default()` function to create an `Engine`:
108
109```rust
110let engine = Engine::default();
111```
112
113An `Engine` is an environment for executing WebAssembly programs.
114Only one `Engine` is needed regardless of how many plugins
115may be executed. For more details, see the [Wasmtime crate documentation](https://docs.rs/wasmtime/latest/wasmtime/#core-concepts).
116
117Next, it passes the engine to the Wasmtime library's `Linker::new()`
118function to create a `Linker`. A `Linker` is parameterized over a state
119type.
120In this application, we don't need per-plugin state
121(plugins implement pure functions), so the state type is `()` (the unit type).
122
123```rust
124let linker: Linker<()> = Linker::new(&engine);
125```
126
127As with the `Engine`, only one `Linker` is needed for the whole application.
128A `Linker` can be used to define functions in the host (in this case, the
129calculator application) that can be called by guests (in this case, plugins
130loaded by the calculator). We don't define any such functions in our host,
131so the linker is only used as an argument to the `instantiate()` function
132(which we'll see a little bit later).
133
134The remaining code checks that the provided path for the plugins directory
135exists and is really a directory, and if so, calls `load_plugin()` on
136each file in the directory that has the `.wasm` extension:
137
138```rust
139    if !plugins_dir.is_dir() {
140        anyhow::bail!("plugins directory does not exist");
141    }
142
143    for entry in fs::read_dir(plugins_dir)? {
144        let path = entry?.path();
145        if path.is_file() && path.extension().and_then(OsStr::to_str) == Some("wasm") {
146            load_plugin(state, &engine, &linker, path)?;
147        }
148    }
149```
150
151Next let's look at the `load_plugin()` function, which loads a single plugin.
152The function begins by calling the Wasmtime library's `Component::from_file()`
153function, which takes an `Engine` and the name of a binary WebAssembly file.
154
155```rust
156let component = Component::from_file(engine, &path)?;
157```
158
159`from_file()` loads and compiles WebAssembly code and creates
160the in-memory representation of a component, which we assign to
161the variable `component`.
162
163The next block of code creates the dynamic representation
164of the component, which has all the resources it needs and can have its
165functions called:
166
167```rust
168    let (plugin_name, plugin, store) = {
169        let mut store = Store::new(engine, ());
170        let plugin = Plugin::instantiate(&mut store, &component, linker)?;
171        (plugin.call_get_plugin_name(&mut store)?, plugin, store)
172    };
173```
174
175First, it calls the Wasmtime library's `Store::new` function to create a `Store`.
176A [`Store`](https://docs.rs/wasmtime/latest/wasmtime/#core-concepts)
177represents the state of a particular component. Unlike an `Engine`, it's specific
178to each unit of code and so it can't be re-used across different plugins.
179The `Store` type is parameterized with a state type `T`, and the third argument
180to `Store::new` must have type `T`.
181Since we don't use host state in this example,
182we pass in `()`, which has type `()`; so we get a `Store<()>` back.
183
184Next, it calls the `Plugin::instantiate()` function, which was generated
185automatically by the `bindgen!("plugin")` macro.
186The function takes the `Store` we just created,
187the `Component` that represents the code for the plugin,
188and the `Linker` that was passed in to `load_plugin()`.
189`instantiate()` returns a `Plugin`.
190The `Plugin` type corresponds to the `plugin` world from our `.wit` file,
191and was also automatically generated by the `bindgen!` macro.
192
193Now that we have a fully instantiated plugin, we can call its
194`get-plugin-name` function. The `call_get_plugin_name` method was
195generated by the `bindgen!` macro; notice that the name
196`call_get_plugin_name` is the same as `get-plugin-name` from the `.wit` file,
197but with underscores in place of hyphens, and is prefixed by `call_`.
198This method takes a `Store`, which in general allows use of host state
199by the implementation of the method in the plugin, though not in this case
200(since we have a `Store<()>`).
201
202Finally, we update the calculator's state by associating the plugin name
203with a structure containing the plugin and store:
204
205```rust
206    state
207        .plugin_descs
208        .insert(plugin_name, PluginDesc { plugin, store });
209```
210
211## Running the code
212
213Finally, this line of code is responsible for actually evaluating the
214expression that was provided on the command line:
215
216```rust
217args.op.run(&mut state)
218```
219
220The `BinaryOperation` struct has a `run` method that looks like this:
221
222```rust
223    fn run(self, state: &mut CalculatorState) -> anyhow::Result<()> {
224        let desc = lookup_plugin(state, self.op.as_ref())?;
225        let result = desc.plugin.call_evaluate(&mut desc.store, self.x, self.y)?;
226        println!("{}({}, {}) = {}", self.op, self.x, self.y, result);
227        Ok(())
228    }
229```
230
231First, it calls `lookup_plugin`, which simply looks up the operation
232name (`self.op`, which is just the name that was given on the command line)
233in the hash table that was created by `load_plugins()`.
234This returns a `PluginDesc`, which is defined as:
235
236```rust
237pub struct PluginDesc {
238    pub plugin: Plugin,
239    pub store: wasmtime::Store<()>,
240}
241```
242
243Remember, the type `Plugin` corresponds to the `plugin` world and was
244generated automatically by the `bindgen!` macro.
245
246The next line of code:
247
248```rust
249let result = desc.plugin.call_evaluate(&mut desc.store, self.x, self.y)?;
250```
251
252is the one that actually calls into the plugin to do the computation.
253The bindings generator guarantees that a `Plugin` has an `evaluate`
254method, which we call using `call_evaluate` (the name it was given
255by the bindings generator).
256Like `call_get_plugin_name`, it takes a `Store` as the first argument.
257The other two arguments are the ones given on the command line.
258`result` is a 32-bit integer (`i32`) because that's the return type
259of `call_evaluate()`.
260
261Finally, we print the result to `stdout`.
262
263## Writing the plugins
264
265So far we've assumed that the `plugins` directory is populated
266with plugins for all the arithmetic operations we want.
267How do we actually write the plugins?
268The [component model documentation](https://component-model.bytecodealliance.org)
269documents how to generate WebAssembly components from various programming languages.
270
271As part of this sample application, two plugins are provided,
272one in `c-plugin/` (implementing the `add` operation), and one in `js-plugin/`
273(implementing the `subtract`) operation.
274The `build.rs` script shows how the code is built.
275
276Any number of plugins could be added, compiled from any language that
277has a toolchain with WebAssembly support, implementing any other arithmetic
278operations.
279
280## Wrapping up
281
282This is a minimal example showing how to embed Wasmtime to create an
283application with dynamically loaded plugins.
284The application could be extended in various ways:
285
286* Allow nested expressions (like `add(subtract(1, 2), 3)`)
287* Add floating-point operations
288* Add unary expressions (like `sqrt(2)`)
289
290The basic mechanism for loading plugins would still be the same,
291with only the application-specific logic changing.
292
293