make ResourceLimiter operate on Store data; add hooks for entering and exiting native code (#2952)

* wasmtime_runtime: move ResourceLimiter defaults into this crate

In preparation of changing wasmtime::ResourceLimiter to be a re-export
of this definition, because translating between two traits was causing
problems elsewhere.

* wasmtime: make ResourceLimiter a re-export of wasmtime_runtime::ResourceLimiter

* refactor Store internals to support ResourceLimiter as part of store's data

* add hooks for entering and exiting native code to Store

* wasmtime-wast, fuzz: changes to adapt ResourceLimiter API

* fix tests

* wrap calls into wasm with entering/exiting exit hooks as well

* the most trivial test found a bug, lets write some more

* store: mark some methods as #[inline] on Store, StoreInner, StoreInnerMost

Co-authored-By: Alex Crichton <alex@alexcrichton.com>

* improve tests for the entering/exiting native hooks

Co-authored-by: Alex Crichton <alex@alexcrichton.com>
This commit is contained in:
Pat Hickey
2021-06-08 07:37:00 -07:00
committed by GitHub
parent ffb92d9109
commit 8b4bdf92e2
17 changed files with 550 additions and 283 deletions

View File

@@ -1,6 +1,4 @@
use anyhow::Result;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst};
use std::sync::Arc;
use wasmtime::*;
#[test]
@@ -11,13 +9,14 @@ fn test_limits() -> Result<()> {
r#"(module (memory (export "m") 0) (table (export "t") 0 anyfunc))"#,
)?;
let mut store = Store::new(&engine, ());
store.limiter(
let mut store = Store::new(
&engine,
StoreLimitsBuilder::new()
.memory_pages(10)
.table_elements(5)
.build(),
);
store.limiter(|s| s as &mut dyn ResourceLimiter);
let instance = Instance::new(&mut store, &module, &[])?;
@@ -72,8 +71,8 @@ fn test_limits_memory_only() -> Result<()> {
r#"(module (memory (export "m") 0) (table (export "t") 0 anyfunc))"#,
)?;
let mut store = Store::new(&engine, ());
store.limiter(StoreLimitsBuilder::new().memory_pages(10).build());
let mut store = Store::new(&engine, StoreLimitsBuilder::new().memory_pages(10).build());
store.limiter(|s| s as &mut dyn ResourceLimiter);
let instance = Instance::new(&mut store, &module, &[])?;
@@ -118,8 +117,8 @@ fn test_initial_memory_limits_exceeded() -> Result<()> {
let engine = Engine::default();
let module = Module::new(&engine, r#"(module (memory (export "m") 11))"#)?;
let mut store = Store::new(&engine, ());
store.limiter(StoreLimitsBuilder::new().memory_pages(10).build());
let mut store = Store::new(&engine, StoreLimitsBuilder::new().memory_pages(10).build());
store.limiter(|s| s as &mut dyn ResourceLimiter);
match Instance::new(&mut store, &module, &[]) {
Ok(_) => unreachable!(),
@@ -148,8 +147,8 @@ fn test_limits_table_only() -> Result<()> {
r#"(module (memory (export "m") 0) (table (export "t") 0 anyfunc))"#,
)?;
let mut store = Store::new(&engine, ());
store.limiter(StoreLimitsBuilder::new().table_elements(5).build());
let mut store = Store::new(&engine, StoreLimitsBuilder::new().table_elements(5).build());
store.limiter(|s| s as &mut dyn ResourceLimiter);
let instance = Instance::new(&mut store, &module, &[])?;
@@ -194,8 +193,8 @@ fn test_initial_table_limits_exceeded() -> Result<()> {
let engine = Engine::default();
let module = Module::new(&engine, r#"(module (table (export "t") 23 anyfunc))"#)?;
let mut store = Store::new(&engine, ());
store.limiter(StoreLimitsBuilder::new().table_elements(4).build());
let mut store = Store::new(&engine, StoreLimitsBuilder::new().table_elements(4).build());
store.limiter(|s| s as &mut dyn ResourceLimiter);
match Instance::new(&mut store, &module, &[]) {
Ok(_) => unreachable!(),
@@ -242,8 +241,8 @@ fn test_pooling_allocator_initial_limits_exceeded() -> Result<()> {
r#"(module (memory (export "m1") 2) (memory (export "m2") 5))"#,
)?;
let mut store = Store::new(&engine, ());
store.limiter(StoreLimitsBuilder::new().memory_pages(3).build());
let mut store = Store::new(&engine, StoreLimitsBuilder::new().memory_pages(3).build());
store.limiter(|s| s as &mut dyn ResourceLimiter);
match Instance::new(&mut store, &module, &[]) {
Ok(_) => unreachable!(),
@@ -262,35 +261,29 @@ fn test_pooling_allocator_initial_limits_exceeded() -> Result<()> {
}
struct MemoryContext {
host_memory_used: AtomicUsize,
wasm_memory_used: AtomicUsize,
host_memory_used: usize,
wasm_memory_used: usize,
memory_limit: usize,
limit_exceeded: AtomicBool,
limiter_dropped: AtomicBool,
limit_exceeded: bool,
}
struct HostMemoryLimiter(Arc<MemoryContext>);
impl ResourceLimiter for HostMemoryLimiter {
impl ResourceLimiter for MemoryContext {
fn memory_growing(&mut self, current: u32, desired: u32, maximum: Option<u32>) -> bool {
// Check if the desired exceeds a maximum (either from Wasm or from the host)
if desired > maximum.unwrap_or(u32::MAX) {
self.0.limit_exceeded.store(true, SeqCst);
self.limit_exceeded = true;
return false;
}
assert_eq!(
current as usize * 0x10000,
self.0.wasm_memory_used.load(SeqCst)
);
assert_eq!(current as usize * 0x10000, self.wasm_memory_used,);
let desired = desired as usize * 0x10000;
if desired + self.0.host_memory_used.load(SeqCst) > self.0.memory_limit {
self.0.limit_exceeded.store(true, SeqCst);
if desired + self.host_memory_used > self.memory_limit {
self.limit_exceeded = true;
return false;
}
self.0.wasm_memory_used.store(desired, SeqCst);
self.wasm_memory_used = desired;
true
}
@@ -299,12 +292,6 @@ impl ResourceLimiter for HostMemoryLimiter {
}
}
impl Drop for HostMemoryLimiter {
fn drop(&mut self) {
self.0.limiter_dropped.store(true, SeqCst);
}
}
#[test]
fn test_custom_limiter() -> Result<()> {
let engine = Engine::default();
@@ -315,18 +302,16 @@ fn test_custom_limiter() -> Result<()> {
linker.func_wrap(
"",
"alloc",
|caller: Caller<'_, Arc<MemoryContext>>, size: u32| -> u32 {
let ctx = caller.data();
|mut caller: Caller<'_, MemoryContext>, size: u32| -> u32 {
let mut ctx = caller.data_mut();
let size = size as usize;
if size + ctx.host_memory_used.load(SeqCst) + ctx.wasm_memory_used.load(SeqCst)
<= ctx.memory_limit
{
ctx.host_memory_used.fetch_add(size, SeqCst);
if size + ctx.host_memory_used + ctx.wasm_memory_used <= ctx.memory_limit {
ctx.host_memory_used += size;
return 1;
}
ctx.limit_exceeded.store(true, SeqCst);
ctx.limit_exceeded = true;
0
},
@@ -337,16 +322,15 @@ fn test_custom_limiter() -> Result<()> {
r#"(module (import "" "alloc" (func $alloc (param i32) (result i32))) (memory (export "m") 0) (func (export "f") (param i32) (result i32) local.get 0 call $alloc))"#,
)?;
let context = Arc::new(MemoryContext {
host_memory_used: AtomicUsize::new(0),
wasm_memory_used: AtomicUsize::new(0),
let context = MemoryContext {
host_memory_used: 0,
wasm_memory_used: 0,
memory_limit: 1 << 20, // 16 wasm pages is the limit for both wasm + host memory
limit_exceeded: AtomicBool::new(false),
limiter_dropped: AtomicBool::new(false),
});
limit_exceeded: false,
};
let mut store = Store::new(&engine, context.clone());
store.limiter(HostMemoryLimiter(context.clone()));
let mut store = Store::new(&engine, context);
store.limiter(|s| s as &mut dyn ResourceLimiter);
let instance = linker.instantiate(&mut store, &module)?;
let memory = instance.get_memory(&mut store, "m").unwrap();
@@ -355,7 +339,7 @@ fn test_custom_limiter() -> Result<()> {
memory.grow(&mut store, 5)?;
memory.grow(&mut store, 2)?;
assert!(!context.limit_exceeded.load(SeqCst));
assert!(!store.data().limit_exceeded);
// Grow the host "memory" by 384 KiB
let f = instance.get_typed_func::<u32, u32, _>(&mut store, "f")?;
@@ -365,7 +349,7 @@ fn test_custom_limiter() -> Result<()> {
assert_eq!(f.call(&mut store, 2 * 0x10000)?, 1);
// Memory is at the maximum, but the limit hasn't been exceeded
assert!(!context.limit_exceeded.load(SeqCst));
assert!(!store.data().limit_exceeded);
// Try to grow the memory again
assert_eq!(
@@ -376,16 +360,14 @@ fn test_custom_limiter() -> Result<()> {
"failed to grow memory by `1`"
);
assert!(context.limit_exceeded.load(SeqCst));
assert!(store.data().limit_exceeded);
// Try to grow the host "memory" again
assert_eq!(f.call(&mut store, 1)?, 0);
assert!(context.limit_exceeded.load(SeqCst));
assert!(store.data().limit_exceeded);
drop(store);
assert!(context.limiter_dropped.load(SeqCst));
Ok(())
}