Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add extending WASI plugin example #399

Merged
merged 14 commits into from
Apr 29, 2019
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ All PRs to the Wasmer repository must add to this file.
Blocks of changes will separated by version increments.

## **[Unreleased]**
- [#399](https://github.com/wasmerio/wasmer/pull/399) Add example of using a plugin extended from WASI
- [#397](https://github.com/wasmerio/wasmer/pull/397) Fix WASI fs abstraction to work on Windows
- [#390](https://github.com/wasmerio/wasmer/pull/390) Pin released wapm version and add it as a git submodule

## 0.4.0 - 2018-04-23

- [#397](https://github.com/wasmerio/wasmer/pull/397) Fix WASI fs abstraction to work on Windows
- [#390](https://github.com/wasmerio/wasmer/pull/390) Pin released wapm version and add it as a git submodule
- [#383](https://github.com/wasmerio/wasmer/pull/383) Hook up wasi exit code to wasmer cli.
- [#382](https://github.com/wasmerio/wasmer/pull/382) Improve error message on `--backend` flag to only suggest currently enabled backends
- [#381](https://github.com/wasmerio/wasmer/pull/381) Allow retrieving propagated user errors.
Expand Down
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ wasmer-llvm-backend = { path = "lib/llvm-backend", optional = true }
wasmer-wasi = { path = "lib/wasi", optional = true }

[workspace]
members = ["lib/clif-backend", "lib/singlepass-backend", "lib/runtime", "lib/runtime-abi", "lib/runtime-core", "lib/emscripten", "lib/spectests", "lib/win-exception-handler", "lib/runtime-c-api", "lib/llvm-backend", "lib/wasi"]
members = ["lib/clif-backend", "lib/singlepass-backend", "lib/runtime", "lib/runtime-abi", "lib/runtime-core", "lib/emscripten", "lib/spectests", "lib/win-exception-handler", "lib/runtime-c-api", "lib/llvm-backend", "lib/wasi", "examples/plugin-for-example"]

[build-dependencies]
wabt = "0.7.2"
Expand All @@ -49,3 +49,7 @@ fast-tests = []
"backend:singlepass" = ["wasmer-singlepass-backend"]
wasi = ["wasmer-wasi"]
vfs = ["wasmer-runtime-abi"]

[[example]]
name = "plugin"
crate-type = ["bin"]
Binary file added examples/plugin-for-example.wasm
Binary file not shown.
7 changes: 7 additions & 0 deletions examples/plugin-for-example/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
MarkMcCaskey marked this conversation as resolved.
Show resolved Hide resolved
name = "plugin-for-example"
version = "0.1.0"
authors = ["The Wasmer Engineering Team <enigneering@wasmer.io>"]
edition = "2018"

[dependencies]
43 changes: 43 additions & 0 deletions examples/plugin-for-example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# WASI plugin example

In this example we extend the imports of Wasmer's WASI ABI to demonstrate how custom plugins work.

See the `wasmer/examples/plugin.rs` file for the source code of the host system.

## Compiling
_Attention Windows users: WASI target only works with the `nightly-x86_64-pc-windows-gnu` toolchain._
```
# Install an up to date version of Rust nightly
# Add the target
rustup target add wasm32-unknown-wasi
# build it
cargo +nightly build --release --target=wasm32-unknown-wasi
# copy it to examples folder
cp ../../target/wasm32-unknown-wasi/release/plugin-for-example.wasm ../
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may also be worth noting that WASI is only supported on windows with the gnu toolchain. If you can't find a way to work this detail into the example, that's ok, but it could be another stumbling block for some.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I'm not sure exactly what to add to say that. What I mean is, does that show up in the target dir?

```

## Running
```
# Go back to top level Wasmer dir
cd ..
# Run the example
cargo run --example plugin
```

## Inspecting the plugin
```
# Install wabt via wapm; installed globally with the `g` flag
wapm install -g wabt
# Turn the binary WASM file in to a readable WAT text file
wapm run wasm2wat examples/plugin-for-example.wasm
```

At the top of the file we can see which functions this plugin expects. Most are covered by WASI, but we handle the rest.

## Explanation

In this example, we instantiate a system with an extended (WASI)[wasi] ABI, allowing our program to rely on Wasmer's implementation of the syscalls defined by WASI as well as our own that we made. This allows us to use the full power of an existing ABI, like WASI, and give it super-powers for our specific use case.

Because the Rust WASI doesn't support the crate type of `cdylib`, we have to include a main function which we don't use. This is being discussed [here](https://github.com/WebAssembly/WASI/issues/24).

[wasi]: https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/
12 changes: 12 additions & 0 deletions examples/plugin-for-example/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
extern "C" {
fn it_works() -> i32;
}

#[no_mangle]
pub fn plugin_entrypoint(n: i32) -> i32 {
println!("Hello from inside WASI");
let result = unsafe { it_works() };
result + n
}

pub fn main() {}
12 changes: 12 additions & 0 deletions examples/plugin-for-example/wapm.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "plugin-for-example"
version = "0.1.0"
description = "A plugin for our example system"
readme = "README.md"
repository = "https://github.com/wasmerio/wasmer/examples/plugin-for-example"
license = "MIT"

[[module]]
name = "plugin-for-example"
source = "../../target/wasm32-unknown-wasi/release/plugin-for-example.wasm"
abi = "none"
38 changes: 38 additions & 0 deletions examples/plugin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use wasmer_runtime::{func, imports, instantiate};
use wasmer_runtime_core::vm::Ctx;
use wasmer_wasi::generate_import_object;

static PLUGIN_LOCATION: &'static str = "examples/plugin-for-example.wasm";

fn it_works(_ctx: &mut Ctx) -> i32 {
println!("Hello from outside WASI");
5
}

fn main() {
// Load the plugin data
let wasm_bytes = std::fs::read(PLUGIN_LOCATION).expect(&format!(
"Could not read in WASM plugin at {}",
PLUGIN_LOCATION
));

// WASI imports
let mut base_imports = generate_import_object(vec![], vec![], vec![]);
// env is the default namespace for extern functions
let custom_imports = imports! {
"env" => {
"it_works" => func!(it_works),
},
};
// The WASI imports object contains all required import functions for a WASI module to run.
// Extend this imports with our custom imports containing "it_works" function so that our custom wasm code may run.
base_imports.extend(custom_imports);
MarkMcCaskey marked this conversation as resolved.
Show resolved Hide resolved
let instance =
instantiate(&wasm_bytes[..], &base_imports).expect("failed to instantiate wasm module");

// get a reference to the function "plugin_entrypoint" which takes an i32 and returns an i32
let entry_point = instance.func::<(i32), i32>("plugin_entrypoint").unwrap();
MarkMcCaskey marked this conversation as resolved.
Show resolved Hide resolved
// call the "entry_point" function in WebAssembly with the number "2" as the i32 argument
let result = entry_point.call(2).expect("failed to execute plugin");
MarkMcCaskey marked this conversation as resolved.
Show resolved Hide resolved
println!("result: {}", result);
}