* Add resource limiting to the Wasmtime API. This commit adds a `ResourceLimiter` trait to the Wasmtime API. When used in conjunction with `Store::new_with_limiter`, this can be used to monitor and prevent WebAssembly code from growing linear memories and tables. This is particularly useful when hosts need to take into account host resource usage to determine if WebAssembly code can consume more resources. A simple `StaticResourceLimiter` is also included with these changes that will simply limit the size of linear memories or tables for all instances created in the store based on static values. * Code review feedback. * Implemented `StoreLimits` and `StoreLimitsBuilder`. * Moved `max_instances`, `max_memories`, `max_tables` out of `Config` and into `StoreLimits`. * Moved storage of the limiter in the runtime into `Memory` and `Table`. * Made `InstanceAllocationRequest` use a reference to the limiter. * Updated docs. * Made `ResourceLimiterProxy` generic to remove a level of indirection. * Fixed the limiter not being used for `wasmtime::Memory` and `wasmtime::Table`. * Code review feedback and bug fix. * `Memory::new` now returns `Result<Self>` so that an error can be returned if the initial requested memory exceeds any limits placed on the store. * Changed an `Arc` to `Rc` as the `Arc` wasn't necessary. * Removed `Store` from the `ResourceLimiter` callbacks. Custom resource limiter implementations are free to capture any context they want, so no need to unnecessarily store a weak reference to `Store` from the proxy type. * Fixed a bug in the pooling instance allocator where an instance would be leaked from the pool. Previously, this would only have happened if the OS was unable to make the necessary linear memory available for the instance. With these changes, however, the instance might not be created due to limits placed on the store. We now properly deallocate the instance on error. * Added more tests, including one that covers the fix mentioned above. * Code review feedback. * Add another memory to `test_pooling_allocator_initial_limits_exceeded` to ensure a partially created instance is successfully deallocated. * Update some doc comments for better documentation of `Store` and `ResourceLimiter`.
85 lines
2.9 KiB
Rust
85 lines
2.9 KiB
Rust
//! An example of how to interact with wasm memory.
|
|
//!
|
|
//! Here a small wasm module is used to show how memory is initialized, how to
|
|
//! read and write memory through the `Memory` object, and how wasm functions
|
|
//! can trap when dealing with out-of-bounds addresses.
|
|
|
|
// You can execute this example with `cargo run --example example`
|
|
|
|
use anyhow::Result;
|
|
use wasmtime::*;
|
|
|
|
fn main() -> Result<()> {
|
|
// Create our `Store` context and then compile a module and create an
|
|
// instance from the compiled module all in one go.
|
|
let wasmtime_store = Store::default();
|
|
let module = Module::from_file(wasmtime_store.engine(), "examples/memory.wat")?;
|
|
let instance = Instance::new(&wasmtime_store, &module, &[])?;
|
|
|
|
// Load up our exports from the instance
|
|
let memory = instance
|
|
.get_memory("memory")
|
|
.ok_or(anyhow::format_err!("failed to find `memory` export"))?;
|
|
let size = instance.get_typed_func::<(), i32>("size")?;
|
|
let load = instance.get_typed_func::<i32, i32>("load")?;
|
|
let store = instance.get_typed_func::<(i32, i32), ()>("store")?;
|
|
|
|
// Note that these memory reads are *unsafe* due to unknown knowledge about
|
|
// aliasing with wasm memory. For more information about the safety
|
|
// guarantees here and how to use `Memory` safely, see the API
|
|
// documentation.
|
|
println!("Checking memory...");
|
|
assert_eq!(memory.size(), 2);
|
|
assert_eq!(memory.data_size(), 0x20000);
|
|
unsafe {
|
|
assert_eq!(memory.data_unchecked_mut()[0], 0);
|
|
assert_eq!(memory.data_unchecked_mut()[0x1000], 1);
|
|
assert_eq!(memory.data_unchecked_mut()[0x1003], 4);
|
|
}
|
|
|
|
assert_eq!(size.call(())?, 2);
|
|
assert_eq!(load.call(0)?, 0);
|
|
assert_eq!(load.call(0x1000)?, 1);
|
|
assert_eq!(load.call(0x1003)?, 4);
|
|
assert_eq!(load.call(0x1ffff)?, 0);
|
|
assert!(load.call(0x20000).is_err()); // out of bounds trap
|
|
|
|
println!("Mutating memory...");
|
|
unsafe {
|
|
memory.data_unchecked_mut()[0x1003] = 5;
|
|
}
|
|
|
|
store.call((0x1002, 6))?;
|
|
assert!(store.call((0x20000, 0)).is_err()); // out of bounds trap
|
|
|
|
unsafe {
|
|
assert_eq!(memory.data_unchecked_mut()[0x1002], 6);
|
|
assert_eq!(memory.data_unchecked_mut()[0x1003], 5);
|
|
}
|
|
assert_eq!(load.call(0x1002)?, 6);
|
|
assert_eq!(load.call(0x1003)?, 5);
|
|
|
|
// Grow memory.
|
|
println!("Growing memory...");
|
|
memory.grow(1)?;
|
|
assert_eq!(memory.size(), 3);
|
|
assert_eq!(memory.data_size(), 0x30000);
|
|
|
|
assert_eq!(load.call(0x20000)?, 0);
|
|
store.call((0x20000, 0))?;
|
|
assert!(load.call(0x30000).is_err());
|
|
assert!(store.call((0x30000, 0)).is_err());
|
|
|
|
assert!(memory.grow(1).is_err());
|
|
assert!(memory.grow(0).is_ok());
|
|
|
|
println!("Creating stand-alone memory...");
|
|
let memorytype = MemoryType::new(Limits::new(5, Some(5)));
|
|
let memory2 = Memory::new(&wasmtime_store, memorytype)?;
|
|
assert_eq!(memory2.size(), 5);
|
|
assert!(memory2.grow(1).is_err());
|
|
assert!(memory2.grow(0).is_ok());
|
|
|
|
Ok(())
|
|
}
|