mirror of
https://github.com/mii443/wasmer.git
synced 2025-12-03 03:08:22 +00:00
doc(migration): Add examples on how to migrate to Wasmer 1.0.0
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# Migrating from Wasmer 0.x to Wasmer 1.0.0
|
||||
|
||||
Wasmer 1.0.0 is currently in alpha and is our primary focus. This document will
|
||||
describe the differences between 0.x Wasmer and Wasmer 1.0.0 and provide examples
|
||||
describe the differences between Wasmer 0.x and Wasmer 1.0.0 and provide examples
|
||||
to make migrating to the new API as simple as possible.
|
||||
|
||||
Some features are still under development during the alpha of Wasmer 1.0.0. This document
|
||||
@@ -9,36 +9,56 @@ will aim to make clear what these features are.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- Rationale for changes in 1.0.0
|
||||
- How to use Wasmer 1.0.0
|
||||
- Project structure
|
||||
- TODO: specific differences
|
||||
- [Rationale for changes in 1.0.0](#rationale-for-changes-in-100)
|
||||
- [How to use Wasmer 1.0.0](#how-to-use-wasmer-100)
|
||||
- [Installing Wasmer CLI](#installing-wamser-cli)
|
||||
- [Using Wasmer 1.0.0](#using-wamser-100)
|
||||
- [Project structure](#project-structure)
|
||||
- [Differences](#differences)
|
||||
- [Instantiating modules](#instantiating-modules)
|
||||
- [Passing host functions](#passing-host-functions)
|
||||
- [Accessing the environment as a host function](#accessing-the-environment-as-a-host-function)
|
||||
- [Error handling](#error-handling)
|
||||
- [Caching modules](#caching-modules)
|
||||
|
||||
## Rationale for changes in 1.0.0
|
||||
|
||||
TODO: explain why 1.0.0 exists
|
||||
Wasmer 0.x was great but as the WASM community and standards evolve we felt the need to make Wasmer also follow these
|
||||
changes.
|
||||
|
||||
Wasmer 1.x is what we think a necessary rewrite of a big part of the project to make it more future-proof.
|
||||
|
||||
This version introduces many new features and makes using Wasmer more natural. We did a hard work making it as close
|
||||
to the standard API as possible while always providing good performances, flexibility and stability.
|
||||
|
||||
The rewrite of the Wasmer Runtime also comes with a rewrite of the languages integrations to achieve the same goals:
|
||||
providing a clearer API and improving the feature set.
|
||||
|
||||
In this document you will discover the major changes between Wasmer 0.x and Wasmer 1.x by highlighting how to use the
|
||||
new Rust API.
|
||||
|
||||
## How to use Wasmer 1.0.0
|
||||
|
||||
### Installing Wasmer
|
||||
### Installing Wasmer CLI
|
||||
|
||||
See [wasmer.io] for installation instructions.
|
||||
|
||||
If you already have wasmer installed, run `wasmer self-update`.
|
||||
|
||||
Install the latest versions of wasmer with [wasmer-nightly].
|
||||
Install the latest versions of Wasmer with [wasmer-nightly] or by following the steps described in the
|
||||
documentation: [Getting Started][getting-started].
|
||||
|
||||
### Using Wasmer 1.0.0
|
||||
|
||||
The CLI interface for Wasmer 1.0.0 is mostly the same as it was in Wasmer 0.X.
|
||||
The CLI interface for Wasmer 1.0.0 is mostly the same as it was in Wasmer 0.x.
|
||||
|
||||
One difference is that rather than specifying the compiler with `--backend=cranelift`,
|
||||
in Wasmer 1.0.0 we prefer using the name of the backend as a flag directly,
|
||||
for example: `--cranelift`.
|
||||
|
||||
The top level crates that users will usually interface with are:
|
||||
The top-level crates that users will usually interface with are:
|
||||
|
||||
- [wasmer] - core API
|
||||
- [wasmer] - Wasmer's core runtime API
|
||||
- [wasmer-wasi] - Wasmer's WASI implementation
|
||||
- [wasmer-emscripten] - Wasmer's Emscripten implementation
|
||||
- TODO:
|
||||
@@ -47,21 +67,22 @@ See the [examples] to find out how to do specific things in Wasmer 1.0.0.
|
||||
|
||||
## Project Structure
|
||||
|
||||

|
||||

|
||||
|
||||
The figure above shows the core Wasmer crates and their dependencies with transitive dependencies deduplicated.
|
||||
|
||||
Wasmer 1.0.0 has two core architectural abstractions: engines and compilers.
|
||||
|
||||
A compiler is a system that translates Wasm into a format that can be understood
|
||||
more directly by a real computer.
|
||||
An engine is a system that processes WASM with a compiler and prepares it to be executed.
|
||||
|
||||
An engine is a system that processes Wasm with a compiler and prepares it to be executed.
|
||||
A compiler is a system that translates WASM into a format that can be understood
|
||||
more directly by a real computer: machine code.
|
||||
|
||||
TODO: better explain what the engine actually does
|
||||
For example, in the [examples] you'll see that we are using the JIT engine and the Cranelift compiler. The JIT engine
|
||||
will generate machine code at runtime, using Cranelift, and then execute it.
|
||||
|
||||
For most uses, users will primarily use the [wasmer] crate directly, perhaps with one of our
|
||||
provided ABIs such as [wasmer-wasi]. However for users that need finer grained control over
|
||||
provided ABIs such as [wasmer-wasi]. However, for users that need finer grained control over
|
||||
the behavior of wasmer, other crates such as [wasmer-compiler] and [wasmer-engine] may be used
|
||||
to implement custom compilers and engines respectively.
|
||||
|
||||
@@ -69,29 +90,244 @@ to implement custom compilers and engines respectively.
|
||||
|
||||
### Instantiating modules
|
||||
|
||||
TODO: link to example, etc.
|
||||
With Wasmer 0.x, instantiating a module was a matter of calling `wasmer::compiler::compile` and then calling
|
||||
`instanciate` on the compiled module.
|
||||
|
||||
While simple, this did not give you full-control over Wasmer's configuration. For example, choosing another compiler
|
||||
was not straightforward.
|
||||
|
||||
With Wasmer 1.x, we changed this part and made the API look more like how Wasmer works internally to give you more
|
||||
control:
|
||||
|
||||
```diff
|
||||
- let module = compile(&wasm_bytes[..])?;
|
||||
+ let engine = JIT::new(&Cranelift::default()).engine();
|
||||
+ let store = Store::new(&engine);
|
||||
+ let module = Module::new(&store, wasm_bytes)?;
|
||||
- let instance = module.instantiate(&imports)?;
|
||||
+ let instance = Instance::new(&module, &import_object)?;
|
||||
```
|
||||
|
||||
Note that we did not cover how to create the import object here. This is because this part works the same as it used to
|
||||
with Wasmer 0.x.
|
||||
|
||||
To get more information on how instantiation now works, have a look at the [dedicated example][instance-example]
|
||||
|
||||
### Passing host functions
|
||||
|
||||
TODO: link to example showing advanced uses of the import object, show some example code inline and compare it to old wasmer
|
||||
With Wasmer 0.x passing host functions to the guest was primarily done using the `func!` macro or by directly using
|
||||
`Func::new` or `DynamicFunc::new`.
|
||||
|
||||
Given we have a function like:
|
||||
|
||||
```rust
|
||||
fn sum(a: i32, b: i32) -> i32 {
|
||||
a + b
|
||||
}
|
||||
```
|
||||
|
||||
We want to import this function in the guest module, let's have a look at how it differs between Wasmer 0.x and
|
||||
Wasmer 1.x:
|
||||
|
||||
```diff
|
||||
let import_object = imports! {
|
||||
"env" => {
|
||||
- "sum" => func!(sum),
|
||||
+ "sum" => Function::new_native(&store, sum),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The above example illustrates how to import what we call "native functions". There were already available in Wasmer
|
||||
0.x through the `func!` macro or with `Func::new`.
|
||||
|
||||
There is a second flavor for imported functions: dynamic functions. With Wasmer 0;x you would have created such a
|
||||
function using `DynamicFunc::new`, here is how it's done with Wasmer 1.x:
|
||||
|
||||
```rust
|
||||
let sum_signature = FunctionType::new(vec![Type::I32, Type::I32], vec![Type::I32]);
|
||||
let sum = Function::new(&store, &sum_signature, |args| {
|
||||
let result = args[0].unwrap_I32() + args[1].unwrap_i32();
|
||||
|
||||
Ok(vec![Value::I32(result)])
|
||||
});
|
||||
```
|
||||
|
||||
Both examples address different needs and have their own pros and cons. We encourage you to have a look at the
|
||||
dedicated example: [Exposing host functions][host-functions-example].
|
||||
|
||||
Note that having this API for functions now looks more like the other entities APIs: globals, memories, tables. Here is
|
||||
a quick example introducing each of them: [Imports & Exports][imports-exports-example]
|
||||
|
||||
### Accessing the environment as a host function
|
||||
|
||||
TODO: link to example showing host functions accessing VM internals such as memory, calling other functions, etc., show some example code inline and compare it to old wasmer
|
||||
With Wasmer 0.x each function had its own `vm::Ctx`. This was your entrypoint to the module internals and allowed
|
||||
access to the context of the currently running instance.
|
||||
|
||||
With Wasmer 1.0.0 this was changed to provide a simpler yet powerful API.
|
||||
|
||||
Let's see an example where we want to have access to the module memory. Here is how we would have done that with Wasmer
|
||||
0.x:
|
||||
|
||||
```diff
|
||||
- let get_at = |ctx: &mut vm::Ctx, idx: i32, len: i32| {
|
||||
- let mem_desc = ctx.memory(0);
|
||||
- let mem = mem_desc.deref();
|
||||
-
|
||||
- println!("Memory: {:?}", mem);
|
||||
-
|
||||
- let view: MemoryView<u8> = mem.view();
|
||||
- let bytes = view[idx as usize..len as usize].iter().map(Cell::get).collect();
|
||||
-
|
||||
- println!("string: {}", String::from_utf8(bytes).unwrap());
|
||||
- };
|
||||
|
||||
- let import_object = imports! {
|
||||
- "env" => {
|
||||
- "get_at" => func!(get_at),
|
||||
- }
|
||||
- };
|
||||
+ let import_object = imports! {};
|
||||
|
||||
- let instance = instantiate(wasm_bytes, &import_object)?;
|
||||
+ let instance = Instance::new(&wasm_bytes, &import_object)?;
|
||||
|
||||
+ let memory = instance.exports.get_memory("mem")?;
|
||||
+ let get = instance
|
||||
+ .exports
|
||||
+ .get_native_function::<(), (WasmPtr<u8, Array>, i32)>("get")?;
|
||||
+ let (ptr, length) = get.call()?;
|
||||
+ let str = ptr.get_utf8_string(memory, length as u32)?;
|
||||
```
|
||||
|
||||
Here we have a module which provides one exported function: `get`. Each time we call this function it will in turn
|
||||
call our imported function `get_at`.
|
||||
|
||||
The `get_at` function is responsible for reading the guest module-s memory through the `vm::Ctx`.
|
||||
|
||||
With Wasmer 1.0.0 (where the `vm::Ctx` does not exist anymore) we can achieve the same result with something more
|
||||
natural: we only use imports and exports to read from the memory and write to it.
|
||||
|
||||
Take a look at the following examples to get more details:
|
||||
* [Interacting with memory][memory]
|
||||
* [Using memory pointers][memory-pointers]
|
||||
|
||||
The other thing where `vm::Ctx` was useful was to pass data from and to host functions. This has also been made simpler
|
||||
with Wasmer 1.x:
|
||||
|
||||
```rust
|
||||
let shared_counter: Arc<RefCell<i32>> = Arc::new(RefCell::new(0));
|
||||
|
||||
struct Env {
|
||||
counter: Arc<RefCell<i32>>,
|
||||
}
|
||||
|
||||
fn get_counter(env: &mut Env) -> i32 {
|
||||
*env.counter.borrow()
|
||||
}
|
||||
|
||||
let get_counter_func = Function::new_native_with_env(
|
||||
&store,
|
||||
Env { counter: shared_counter.clone() },
|
||||
get_counter
|
||||
);
|
||||
```
|
||||
|
||||
A dedicated example describes how to use this feature: [Exposing host functions][host-functions].
|
||||
|
||||
### Error handling
|
||||
|
||||
TODO: link to example doing error handling, show inline what it looks like and compare it to old wasmer
|
||||
Handling errors with Wasmer 0.x was a bit hard, especially, the `wasmer_runtime::error::RuntimeError`. It was rather
|
||||
complex: it had many variants that you had to handle when pattern matching results. This has been made way simpler with
|
||||
Wasmer 1.0.0:
|
||||
|
||||
```diff
|
||||
// Retrieve the `get` function from module's exports and then call it
|
||||
let result = get.call(0, 13);
|
||||
|
||||
match result {
|
||||
- Err(RuntimeError::InvokeError(InvokeError::TrapCode { .. })) => {
|
||||
- // ...
|
||||
- }
|
||||
- Err(RuntimeError::InvokeError(InvokeError::FailedWithNoError)) => {
|
||||
- // ...
|
||||
- }
|
||||
- Err(RuntimeError::InvokeError(InvokeError::UnknownTrap { .. })) => {
|
||||
- // ...
|
||||
- }
|
||||
- Err(RuntimeError::InvokeError(InvokeError::UnknownTrapCode { .. })) => {
|
||||
- // ...
|
||||
- }
|
||||
- Err(RuntimeError::InvokeError(InvokeError::EarlyTrap(_))) => {
|
||||
- // ...
|
||||
- }
|
||||
- Err(RuntimeError::InvokeError(InvokeError::Breakpoint(_))) => {
|
||||
- // ...
|
||||
- }
|
||||
- Err(RuntimeError::Metering(_)) => {
|
||||
- // ...
|
||||
- }
|
||||
- Err(RuntimeError::InstanceImage(_)) => {
|
||||
- // ...
|
||||
- }
|
||||
- Err(RuntimeError::User(_)) => {
|
||||
- // ...
|
||||
- }
|
||||
+ Error(e) => {
|
||||
+ println!("Error caught from `div_by_zero`: {}", e.message());
|
||||
+
|
||||
+ let frames = e.trace();
|
||||
+ let frames_len = frames.len();
|
||||
+
|
||||
+ // ...
|
||||
+ }
|
||||
Ok(_) => {
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
As you can see here, handling errors is really easy now! You may find the following examples useful to get more familiar
|
||||
with this topic:
|
||||
* [Handling Errors][errors]
|
||||
* [Interrupting Execution][exit-early]
|
||||
|
||||
Note than with Wasmer 1.0.0, each function that is part of the API has its own kind of error. For example:
|
||||
* Instantiating a module may return `InstantiationError`s;
|
||||
* Getting exports from the guest module may return `ExportError`s;
|
||||
* Calling a exported function may return `RuntimeError`s;
|
||||
* ...
|
||||
|
||||
### Caching modules
|
||||
|
||||
TODO: link to example, etc.
|
||||
You may be aware Wasmer, since 0.x, allows you to cache compiled module so that further executions of your program
|
||||
will be faster.
|
||||
|
||||
### Metering
|
||||
Because caching may bring a significant boost when running Wasm modules we wanted to make it easier to use with
|
||||
Wasmer 1.0.0.
|
||||
|
||||
TODO: link to example, etc.
|
||||
With Wasmer 0.x you had to handle the whole caching process inside your program's code. With Wasmer 1.0.0
|
||||
you'll be able to delegate most of the work to Wasmer:
|
||||
|
||||
[examples]: https://github.com/wasmerio/wasmer/tree/master/examples
|
||||
```diff
|
||||
- let artifact = module.cache().unwrap();
|
||||
- let bytes = artifact.serialize().unwrap();
|
||||
-
|
||||
- let path = "module_cached.so";
|
||||
- fs::write(path, bytes).unwrap();
|
||||
+ module.serialize_to_file(path)?;
|
||||
|
||||
- let mut file = File::open(path).unwrap();
|
||||
- let cached_bytes = &mut vec![];
|
||||
- file.read_to_end(cached_bytes);
|
||||
- drop(file);
|
||||
-
|
||||
- let cached_artifact = Artifact::deserialize(&cached_bytes).unwrap();
|
||||
- let cached_module = unsafe { load_cache_with(cached_artifact, &default_compiler()) }.unwrap();
|
||||
+ let cached_module = unsafe { Module::deserialize_from_file(&store, path) }?;
|
||||
```
|
||||
|
||||
[examples]: https://docs.wasmer.io/integrations/examples
|
||||
[wasmer]: https://crates.io/crates/wasmer/1.0.0-alpha3
|
||||
[wasmer-wasi]: https://crates.io/crates/wasmer-wasi/1.0.0-alpha3
|
||||
[wasmer-emscripten]: https://crates.io/crates/wasmer-emscripten/1.0.0-alpha3
|
||||
@@ -99,3 +335,12 @@ TODO: link to example, etc.
|
||||
[wasmer-compiler]: https://crates.io/crates/wasmer-compiler/1.0.0-alpha3
|
||||
[wasmer.io]: https://wasmer.io
|
||||
[wasmer-nightly]: https://github.com/wasmerio/wasmer-nightly/
|
||||
[getting-started]: https://docs.wasmer.io/ecosystem/wasmer/getting-started
|
||||
[instance-example]: https://docs.wasmer.io/integrations/examples/instance
|
||||
[imports-exports-example]: https://docs.wasmer.io/integrations/examples/imports-and-exports
|
||||
[host-functions-example]: https://docs.wasmer.io/integrations/examples/host-functions
|
||||
[memory]: https://docs.wasmer.io/integrations/examples/memory
|
||||
[memory-pointers]: https://docs.wasmer.io/integrations/examples/memory-pointers
|
||||
[host-functions]: https://docs.wasmer.io/integrations/examples/host-functions
|
||||
[errors]: https://docs.wasmer.io/integrations/examples/errors
|
||||
[exit-early]: https://docs.wasmer.io/integrations/examples/exit-early
|
||||
Reference in New Issue
Block a user