diff --git a/crates/host/Cargo.toml b/crates/host/Cargo.toml index bb851262..131b1e85 100644 --- a/crates/host/Cargo.toml +++ b/crates/host/Cargo.toml @@ -14,7 +14,6 @@ holochain_serialized_bytes = "=0.0.53" serde = "1" tracing = "0.1" parking_lot = "0.12" -once_cell = "1" rand = "0.8" bimap = "0.6" bytes = "1" diff --git a/crates/host/src/env.rs b/crates/host/src/env.rs index 27d233a2..ba04689e 100644 --- a/crates/host/src/env.rs +++ b/crates/host/src/env.rs @@ -2,15 +2,19 @@ use std::num::TryFromIntError; use crate::guest::read_bytes; use crate::prelude::*; +use wasmer::Global; use wasmer::Memory; use wasmer::StoreMut; use wasmer::TypedFunction; +use wasmer_middlewares::metering::MeteringPoints; #[derive(Clone, Default)] pub struct Env { pub memory: Option, pub allocate: Option>, pub deallocate: Option>, + pub wasmer_metering_points_exhausted: Option, + pub wasmer_metering_remaining_points: Option, } impl Env { @@ -97,4 +101,84 @@ impl Env { } } } + + /// Mimics upstream function of the same name but accesses the global directly from env. + /// https://github.com/wasmerio/wasmer/blob/master/lib/middlewares/src/metering.rs#L285 + pub fn get_remaining_points( + &self, + store_mut: &mut StoreMut, + ) -> Result { + let exhausted: i32 = self + .wasmer_metering_points_exhausted + .as_ref() + .ok_or(wasm_error!(WasmErrorInner::Memory))? + .get(store_mut) + .try_into() + .map_err(|_| wasm_error!(WasmErrorInner::PointerMap))?; + + if exhausted > 0 { + return Ok(MeteringPoints::Exhausted); + } + + let points = self + .wasmer_metering_remaining_points + .as_ref() + .ok_or(wasm_error!(WasmErrorInner::Memory))? + .get(store_mut) + .try_into() + .map_err(|_| wasm_error!(WasmErrorInner::PointerMap))?; + + Ok(MeteringPoints::Remaining(points)) + } + + pub fn set_remaining_points( + &self, + store_mut: &mut StoreMut, + points: u64, + ) -> Result<(), wasmer::RuntimeError> { + self.wasmer_metering_remaining_points + .as_ref() + .ok_or(wasm_error!(WasmErrorInner::Memory))? + .set(store_mut, points.into()) + .map_err(|_| wasm_error!(WasmErrorInner::PointerMap))?; + + self.wasmer_metering_points_exhausted + .as_ref() + .ok_or(wasm_error!(WasmErrorInner::Memory))? + .set(store_mut, 0i32.into()) + .map_err(|_| wasm_error!(WasmErrorInner::PointerMap))?; + Ok(()) + } + + pub fn decrease_points( + &self, + store_mut: &mut StoreMut, + points: u64, + ) -> Result { + match self.get_remaining_points(store_mut) { + Ok(MeteringPoints::Remaining(remaining)) => { + if remaining < points { + self.wasmer_metering_remaining_points + .as_ref() + .ok_or(wasm_error!(WasmErrorInner::Memory))? + .set(store_mut, 0i32.into()) + .map_err(|_| wasm_error!(WasmErrorInner::PointerMap))?; + self.wasmer_metering_points_exhausted + .as_ref() + .ok_or(wasm_error!(WasmErrorInner::Memory))? + .set(store_mut, 1i32.into()) + .map_err(|_| wasm_error!(WasmErrorInner::PointerMap))?; + Ok(MeteringPoints::Exhausted) + } else { + self.wasmer_metering_remaining_points + .as_ref() + .ok_or(wasm_error!(WasmErrorInner::Memory))? + .set(store_mut, (remaining - points).into()) + .map_err(|_| wasm_error!(WasmErrorInner::PointerMap))?; + Ok(MeteringPoints::Remaining(remaining - points)) + } + } + v => v, + } + } } diff --git a/crates/host/src/module.rs b/crates/host/src/module.rs index b14cc17a..77243e37 100644 --- a/crates/host/src/module.rs +++ b/crates/host/src/module.rs @@ -3,9 +3,7 @@ use crate::prelude::*; use bimap::BiMap; use bytes::Bytes; use holochain_wasmer_common::WasmError; -use once_cell::sync::{Lazy, OnceCell}; use parking_lot::Mutex; -use parking_lot::RwLock; use std::collections::BTreeMap; use std::sync::Arc; use wasmer::Cranelift; @@ -123,8 +121,6 @@ pub struct SerializedModuleCache { cranelift: fn() -> Cranelift, } -pub static SERIALIZED_MODULE_CACHE: OnceCell> = OnceCell::new(); - impl PlruCache for SerializedModuleCache { type Item = SerializedModule; @@ -214,8 +210,6 @@ pub struct InstanceCache { key_map: PlruKeyMap, cache: BTreeMap>, } -pub static INSTANCE_CACHE: Lazy> = - Lazy::new(|| RwLock::new(InstanceCache::default())); impl PlruCache for InstanceCache { type Item = InstanceWithStore; diff --git a/test/Cargo.lock b/test/Cargo.lock index 7498ed2e..d1619ff0 100644 --- a/test/Cargo.lock +++ b/test/Cargo.lock @@ -776,7 +776,6 @@ dependencies = [ "bytes", "holochain_serialized_bytes", "holochain_wasmer_common", - "once_cell", "parking_lot", "rand", "serde", @@ -1592,6 +1591,7 @@ dependencies = [ "holochain_serialized_bytes", "holochain_wasmer_common", "holochain_wasmer_host", + "once_cell", "parking_lot", "rand", "serde", diff --git a/test/Cargo.toml b/test/Cargo.toml index 30e10c21..ccc1278a 100644 --- a/test/Cargo.toml +++ b/test/Cargo.toml @@ -17,6 +17,7 @@ parking_lot = "0.12" wasmer = "=4.2.2" wasmer-middlewares = "=4.2.2" test-fuzz = "=3.0.4" +once_cell = "1" [dev-dependencies] env_logger = "0.8" diff --git a/test/benches/bench.rs b/test/benches/bench.rs index 86dc992e..297db731 100644 --- a/test/benches/bench.rs +++ b/test/benches/bench.rs @@ -116,7 +116,7 @@ pub fn wasm_call(c: &mut Criterion) { pub fn wasm_call_n(c: &mut Criterion) { let mut group = c.benchmark_group("wasm_call_n"); - let instance_with_store = TestWasm::Io.instance(); + let instance_with_store = TestWasm::Io.unmetered_instance(); macro_rules! bench_n { ( $fs:expr; $t:ty; ) => { @@ -165,7 +165,7 @@ pub fn wasm_call_n(c: &mut Criterion) { pub fn test_process_string(c: &mut Criterion) { let mut group = c.benchmark_group("test_process_string"); - let instance_with_store = TestWasm::Test.instance(); + let instance_with_store = TestWasm::Test.unmetered_instance(); for n in vec![0, 1, 1_000, 1_000_000] { group.throughput(Throughput::Bytes(n)); @@ -203,7 +203,7 @@ pub fn test_instances(c: &mut Criterion) { let mut jhs = Vec::new(); for _ in 0..25 { let input = input.clone(); - let instance_with_store = TestWasm::Test.instance(); + let instance_with_store = TestWasm::Test.unmetered_instance(); let instance = instance_with_store.instance.clone(); let store = instance_with_store.store.clone(); let jh = std::thread::spawn(move || { diff --git a/test/src/import.rs b/test/src/import.rs index cba757cd..1a3e88ef 100644 --- a/test/src/import.rs +++ b/test/src/import.rs @@ -1,4 +1,5 @@ use crate::debug; +use crate::decrease_points; use crate::err; use crate::pages; use crate::short_circuit; @@ -34,6 +35,11 @@ pub fn imports(store: &mut StoreMut, function_env: &FunctionEnv) -> Imports function_env, debug ), + "__hc__decrease_points_1" => Function::new_typed_with_env( + store, + function_env, + decrease_points + ), "__hc__guest_err_1" => Function::new_typed_with_env( store, function_env, diff --git a/test/src/test.rs b/test/src/test.rs index f87bd734..b5505d4a 100644 --- a/test/src/test.rs +++ b/test/src/test.rs @@ -4,6 +4,7 @@ pub mod wasms; use holochain_wasmer_host::prelude::*; use test_common::SomeStruct; use wasmer::FunctionEnvMut; +use wasmer_middlewares::metering::MeteringPoints; pub fn short_circuit( _env: FunctionEnvMut, @@ -49,6 +50,31 @@ pub fn debug( Ok(()) } +pub fn decrease_points( + mut function_env: FunctionEnvMut, + guest_ptr: GuestPtr, + len: Len, +) -> Result { + let (env, mut store_mut) = function_env.data_and_store_mut(); + let points: u64 = env.consume_bytes_from_guest(&mut store_mut, guest_ptr, len)?; + let points_before = env.get_remaining_points(&mut store_mut)?; + let remaining_points = env.decrease_points(&mut store_mut, points)?; + let points_after = env.get_remaining_points(&mut store_mut)?; + assert_eq!(points_after, remaining_points); + env.move_data_to_guest( + &mut store_mut, + Ok::<(u64, u64), WasmError>(match (points_before, remaining_points) { + ( + MeteringPoints::Remaining(points_before), + MeteringPoints::Remaining(remaining_points), + ) => (points_before, remaining_points), + // This will error on the guest because it will require at least 1 point + // to deserialize this value. + _ => (0, 0), + }), + ) +} + pub fn err(_: FunctionEnvMut) -> Result<(), wasmer::RuntimeError> { Err(wasm_error!(WasmErrorInner::Guest("oh no!".into())).into()) } @@ -91,7 +117,8 @@ pub mod tests { vec![ "__hc__short_circuit_5".to_string(), "__hc__test_process_string_2".to_string(), - "__hc__test_process_struct_2".to_string() + "__hc__test_process_struct_2".to_string(), + "__hc__decrease_points_1".to_string(), ], module .imports() @@ -289,7 +316,7 @@ pub mod tests { Err(runtime_error) => assert_eq!( WasmError { file: "src/wasm.rs".into(), - line: 100, + line: 101, error: WasmErrorInner::Guest("oh no!".into()), }, runtime_error.downcast().unwrap(), @@ -330,7 +357,7 @@ pub mod tests { assert_eq!( WasmError { file: "src/wasm.rs".into(), - line: 128, + line: 129, error: WasmErrorInner::Guest("it fails!: ()".into()), }, runtime_error.downcast().unwrap(), @@ -339,4 +366,42 @@ pub mod tests { Ok(_) => unreachable!(), }; } + + #[test] + fn decrease_points_test() { + let InstanceWithStore { store, instance } = TestWasm::Test.instance(); + let dec_by = 1_000_000_u64; + let points_before: u64 = instance + .exports + .get_global("wasmer_metering_remaining_points") + .unwrap() + .get(&mut store.lock().as_store_mut()) + .unwrap_i64() + .try_into() + .unwrap(); + + let (before_decrease, after_decrease): (u64, u64) = guest::call( + &mut store.lock().as_store_mut(), + instance.clone(), + "decrease_points", + dec_by, + ) + .unwrap(); + + let points_after: u64 = instance + .exports + .get_global("wasmer_metering_remaining_points") + .unwrap() + .get(&mut store.lock().as_store_mut()) + .unwrap_i64() + .try_into() + .unwrap(); + + assert!(before_decrease - after_decrease == dec_by); + assert!( + points_before > before_decrease + && before_decrease > after_decrease + && after_decrease > points_after + ); + } } diff --git a/test/src/wasms.rs b/test/src/wasms.rs index be483a23..33cb3b5e 100644 --- a/test/src/wasms.rs +++ b/test/src/wasms.rs @@ -1,9 +1,11 @@ use crate::import::imports; +use holochain_wasmer_host::module::InstanceCache; use holochain_wasmer_host::module::InstanceWithStore; use holochain_wasmer_host::module::ModuleWithStore; use holochain_wasmer_host::module::SerializedModuleCache; -use holochain_wasmer_host::module::SERIALIZED_MODULE_CACHE; use holochain_wasmer_host::prelude::*; +use once_cell::sync::{Lazy, OnceCell}; +use parking_lot::RwLock; use std::sync::Arc; use wasmer::wasmparser::Operator; use wasmer::AsStoreMut; @@ -21,6 +23,12 @@ pub enum TestWasm { Memory, } +pub static SERIALIZED_MODULE_CACHE: OnceCell> = OnceCell::new(); +pub static SERIALIZED_MODULE_CACHE_UNMETERED: OnceCell> = + OnceCell::new(); +pub static INSTANCE_CACHE: Lazy> = + Lazy::new(|| RwLock::new(InstanceCache::default())); + impl TestWasm { pub fn bytes(&self) -> &[u8] { match self { @@ -65,13 +73,21 @@ impl TestWasm { } } + pub fn module_cache(&self, metered: bool) -> &OnceCell> { + if metered { + &SERIALIZED_MODULE_CACHE + } else { + &SERIALIZED_MODULE_CACHE_UNMETERED + } + } + pub fn module(&self, metered: bool) -> Arc { - match SERIALIZED_MODULE_CACHE.get() { + match self.module_cache(metered).get() { Some(cache) => cache.write().get(self.key(metered), self.bytes()).unwrap(), None => { let cranelift_fn = || { let cost_function = |_operator: &Operator| -> u64 { 1 }; - let metering = Arc::new(Metering::new(10000000000, cost_function)); + let metering = Arc::new(Metering::new(10_000_000_000, cost_function)); let mut cranelift = Cranelift::default(); cranelift.canonicalize_nans(true).push_middleware(metering); cranelift @@ -86,7 +102,7 @@ impl TestWasm { // This will error if the cache is already initialized // which could happen if two tests are running in parallel. // It doesn't matter which one wins, so we just ignore the error. - let _did_init_ok = SERIALIZED_MODULE_CACHE.set(parking_lot::RwLock::new( + let _did_init_ok = self.module_cache(metered).set(parking_lot::RwLock::new( SerializedModuleCache::default_with_cranelift(if metered { cranelift_fn } else { @@ -130,6 +146,22 @@ impl TestWasm { .get_typed_function(&store_mut, "__hc__allocate_1") .unwrap(), ); + if metered { + data_mut.wasmer_metering_points_exhausted = Some( + instance + .exports + .get_global("wasmer_metering_points_exhausted") + .unwrap() + .clone(), + ); + data_mut.wasmer_metering_remaining_points = Some( + instance + .exports + .get_global("wasmer_metering_remaining_points") + .unwrap() + .clone(), + ); + } } InstanceWithStore { diff --git a/test/test_wasm/src/wasm.rs b/test/test_wasm/src/wasm.rs index a8906bc2..f00b00e4 100644 --- a/test/test_wasm/src/wasm.rs +++ b/test/test_wasm/src/wasm.rs @@ -11,7 +11,8 @@ host_externs!( this_func_doesnt_exist_but_we_can_extern_it_anyway:1, test_process_string:2, test_process_struct:2, - short_circuit:5 + short_circuit:5, + decrease_points:1 ); #[no_mangle] @@ -133,4 +134,17 @@ pub extern "C" fn try_ptr_fails_fast(guest_ptr: usize, len: usize) -> DoubleUSiz pub extern "C" fn loop_forever(_guest_ptr: usize, _len: usize) -> DoubleUSize { #[allow(clippy::empty_loop)] loop {} +} + +#[no_mangle] +pub extern "C" fn decrease_points(guest_ptr: usize, len: usize) -> DoubleUSize { + let input: u64 = match host_args(guest_ptr, len) { + Ok(v) => v, + Err(err_ptr) => return err_ptr, + }; + let result: (u64, u64) = try_ptr!( + host_call(__hc__decrease_points_1, input), + "could not decrease points" + ); + return_ptr(result) } \ No newline at end of file