1822: fix(vm) Fix memory leak of `InstanceHandle` r=syrusakbary a=Hywan # Description In `wasmer_engine::Artifact::instantiate`, the engine artifact allocates a new instance by calling `wasmer_vm::InstanceHandle::allocate_instance`. To make it short, this function calculates the [`Layout`] of an `wasmer_vm::Instance`, and allocates space for it by calling [`alloc`]. This last part is important because it means we are responsible to deallocate it with [`dealloc`]. The pointer to this `wasmer_vm::Instance` is stored in the `wasmer_vm::InstanceHandle` structure. That's the handle that is returned by the engine artifact `Artifact::instantiate` method. This instance handle is then stored in `wasmer::Instance` (through the intervention of `wasmer::Module`). How is it freed? It wasn't. That's the leak. [`dealloc`] was never called. How to free it? We must call [`dealloc`]. There is even a `wasmer_vm::InstanceHandle::deallocate` helper to do that properly. Neat! When to free it? That's the tricky part. A `wasmer::Instance` can be clonable. To do so, `wasmer_vm::InstanceHandle` must be clonable too. There was a `Clone` implementation, that was constructing a new `wasmer_vm:InstanceHandle` by using the same pointer to `wasmer_vm::Instance`. That's annoying because it's the best way to get many pointers that point to the same `wasmer_vm::Instance` in the nature, and it's difficult to track them. This patch changes the paradigm. There is only one and unique `wasmer_vm::InstanceHandle` per `wasmer::Instance`, including its clones. The handle is now stored inside a `Arc<Mutex<wasmer_vm::InstanceHandle>>`. Consequently, when a `wasmer::Instance` is cloned, it uses the same `wasmer_vm::InstanceHandle`, not a clone of it. Bonus: `wasmer::Instance` continues to be `Send` + `Sync`. So. Let's back to our question. When to free `wasmer_vm::InstanceHandle`? Response: When `wasmer::Instance` is dropped. Right? There is a unique path from `wasmer::Instance`, to `wasmer_vm::InstanceHandle`, to `wasmer_vm::Instance` now. So we just need to call `wasmer_vm::InstanceHandle::dealloc` in a specific `Drop` implementation for `wasmer_vm::InstanceHandle`, and the Rust borrow checker does the rest. Yes. … No. There is another use case: It is possible to create a `wasmer_vm::InstanceHandle` with `InstanceHandle::from_vmctx`. Indeed, a `wasmer_vm::VMContext` also stores a pointer to `wasmer_vm::Instance`. In this, we consider `wasmer_vm::VMContext` owns the instance pointer, somehow, and is responsible to free it properly. Consequently, we need another flag inside `wasmer_vm::InstanceHandle` to know whether this structure owns the pointer to `wasmer_vm::Instance` or not. So. Let's back to our question. When to free `wasmer_vm::InstanceHandle`? Response: Inside the `Drop` implementation of `wasmer_vm::InstanceHandle` with its `Self::dealloc` method if and only if the handle owns the pointer to `wasmer_vm::Instance`. Testing with Valgrind shows that the leak has been removed. [`Layout`]: https://doc.rust-lang.org/std/alloc/struct.Layout.html [`alloc`]: https://doc.rust-lang.org/std/alloc/fn.alloc.html [`dealloc`]: https://doc.rust-lang.org/std/alloc/fn.dealloc.html # Review - [ ] Add a short description of the the change to the CHANGELOG.md file Co-authored-by: Ivan Enderlin <ivan@mnt.io>
wasmer

Wasmer is the most popular WebAssembly
runtime for Rust (...and also the fastest!). It supports JIT (Just in Time) and AOT (Ahead of time)
compilation as well as pluggable compilers suited to your needs.
It's designed to be safe and secure, and runnable in any kind of environment.
Usage
Add to your Cargo.toml
[dependencies]
wasmer = "1.0.0-alpha"
use wasmer::{Store, Module, Instance, Value, imports};
fn main() -> anyhow::Result<()> {
let module_wat = r#"
(module
(type $t0 (func (param i32) (result i32)))
(func $add_one (export "add_one") (type $t0) (param $p0 i32) (result i32)
get_local $p0
i32.const 1
i32.add))
"#;
let store = Store::default();
let module = Module::new(&store, &module_wat);
// The module doesn't import anything, so we create an empty import object.
let import_object = imports! {};
let instance = Instance::new(&module, &import_object)?;
let add_one = instance.exports.get_function("add_one")?;
let result = add_one.call([Value::I32(42)])?;
assert_eq!(result[0], Value::I32(43));
Ok(())
}
Features
Wasmer is not only fast, but also designed to be highly customizable:
- Pluggable Engines: do you have a fancy
dlopenimplementation? This is for you! - Pluggable Compilers: you want to emit code with DynASM or other compiler? We got you!
- Headless mode: that means that no compilers will be required
to run a
serializedModule (viaModule::deserialize()). - Cross-compilation: You can pre-compile a module and serialize it
to then run it in other platform (via
Module::serialize()).
Config flags
Wasmer has the following configuration flags:
wat(enabled by default): It allows to read WebAssembly files in their text format. This feature is normally used only in development environments- Compilers (mutually exclusive):
singlepass: it will usewasmer-compiler-singlepassas the default compiler (ideal for blockchains).cranelift: it will usewasmer-compiler-craneliftas the default compiler (ideal for development).llvm: it will usewasmer-compiler-llvmas the default compiler (ideal for production).
Wasmer ships by default with the cranelift compiler as its great for development proposes.
However, we strongly encourage to use the llvm backend in production as it performs
about 50% faster, achieving near-native speeds.
Note: if you want to use multiple compilers at the same time, it's also possible! You will need to import them directly via each of the compiler crates.
Made with ❤️ by the Wasmer team, for the community