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