diff --git a/substrate/frame/contracts/src/benchmarking/host_sandbox.rs b/substrate/frame/contracts/src/benchmarking/host_sandbox.rs new file mode 100644 index 0000000000000..bdbd86be43898 --- /dev/null +++ b/substrate/frame/contracts/src/benchmarking/host_sandbox.rs @@ -0,0 +1,68 @@ +use crate::{ + benchmarking::{Contract, WasmModule}, + exec::Stack, + storage::meter::Meter, + wasm::Runtime, + BalanceOf, Config, Determinism, GasMeter, Origin, Schedule, TypeInfo, WasmBlob, Weight, +}; +use codec::{Encode, HasCompact}; +use core::fmt::Debug; +use sp_core::Get; +use sp_std::prelude::*; +use wasmi::{Func, Store}; + +type StackExt<'a, T> = Stack<'a, T, WasmBlob>; + +/// A sandbox used for benchmarking host functions. +pub struct Sandbox { + caller: T::AccountId, + dest: T::AccountId, + gas_meter: GasMeter, + storage_meter: Meter, + schedule: Schedule, +} + +impl Sandbox +where + T: Config + pallet_balances::Config, + as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode, +{ + /// Create a new sandbox for the given module. + pub fn new(module: WasmModule) -> &'static mut Self { + let instance = Contract::::new(module.clone(), vec![]).unwrap(); + let caller = instance.caller.clone(); + let dest = instance.account_id.clone(); + + let sandbox = Self { + caller, + dest, + schedule: T::Schedule::get(), + gas_meter: GasMeter::new(Weight::MAX), + storage_meter: Default::default(), + }; + + // TODO: Clean up the leak + Box::leak(Box::new(sandbox)) + } + + /// Prepare a call to the module. + pub fn prepare_call( + &'static mut self, + input: Vec, + ) -> (Func, Store>>) { + let (ext, module) = Stack::bench_new_call( + self.dest.clone(), + Origin::from_account_id(self.caller.clone()), + &mut self.gas_meter, + &mut self.storage_meter, + &self.schedule, + 0u32.into(), + None, + Determinism::Enforced, + ); + + // TODO: Clean up the leak + let ext: &mut StackExt<'static, T> = Box::leak(Box::new(ext)); + module.bench_prepare_call(ext, input) + } +} diff --git a/substrate/frame/contracts/src/benchmarking/mod.rs b/substrate/frame/contracts/src/benchmarking/mod.rs index 9fb107537ba07..a402d0d62eea1 100644 --- a/substrate/frame/contracts/src/benchmarking/mod.rs +++ b/substrate/frame/contracts/src/benchmarking/mod.rs @@ -19,6 +19,7 @@ #![cfg(feature = "runtime-benchmarks")] mod code; +mod host_sandbox; mod sandbox; use self::{ code::{ @@ -796,12 +797,14 @@ mod benchmarks { } #[benchmark(pov_mode = Measured)] - fn seal_now(r: Linear<0, API_BENCHMARK_RUNS>) -> Result<(), BenchmarkError> { - let instance = Contract::::new(WasmModule::getter("seal0", "seal_now", r), vec![])?; - let origin = RawOrigin::Signed(instance.caller.clone()); - #[extrinsic_call] - call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]); - Ok(()) + fn seal_now(r: Linear<0, API_BENCHMARK_RUNS>) { + use host_sandbox::Sandbox; + let sbox = Sandbox::::new(WasmModule::getter("seal0", "seal_now", r)); + let (func, mut store) = sbox.prepare_call(vec![]); + #[block] + { + func.call(&mut store, &[], &mut []).unwrap(); + } } #[benchmark(pov_mode = Measured)] diff --git a/substrate/frame/contracts/src/exec.rs b/substrate/frame/contracts/src/exec.rs index 41a0383811fdb..31cdadb4bb437 100644 --- a/substrate/frame/contracts/src/exec.rs +++ b/substrate/frame/contracts/src/exec.rs @@ -733,6 +733,30 @@ where stack.run(executable, input_data).map(|ret| (account_id, ret)) } + #[cfg(feature = "runtime-benchmarks")] + pub fn bench_new_call( + dest: T::AccountId, + origin: Origin, + gas_meter: &'a mut GasMeter, + storage_meter: &'a mut storage::meter::Meter, + schedule: &'a Schedule, + value: BalanceOf, + debug_message: Option<&'a mut DebugBufferVec>, + determinism: Determinism, + ) -> (Self, E) { + Self::new( + FrameArgs::Call { dest, cached_info: None, delegated_call: None }, + origin, + gas_meter, + storage_meter, + schedule, + value, + debug_message, + determinism, + ) + .unwrap() + } + /// Create a new call stack. fn new( args: FrameArgs, diff --git a/substrate/frame/contracts/src/wasm/mod.rs b/substrate/frame/contracts/src/wasm/mod.rs index e6af62c72891d..c9ceae3da5ed3 100644 --- a/substrate/frame/contracts/src/wasm/mod.rs +++ b/substrate/frame/contracts/src/wasm/mod.rs @@ -337,26 +337,49 @@ impl CodeInfo { } } -impl Executable for WasmBlob { - fn from_storage( - code_hash: CodeHash, - gas_meter: &mut GasMeter, - ) -> Result { - let code_info = >::get(code_hash).ok_or(Error::::CodeNotFound)?; - gas_meter.charge(CodeLoadToken(code_info.code_len))?; - let code = >::get(code_hash).ok_or(Error::::CodeNotFound)?; - Ok(Self { code, code_info, code_hash }) +use crate::{ExecError, ExecReturnValue}; +use wasmi::Func; +enum InstanceOrExecReturn<'a, E: Ext> { + Instance((Func, Store>)), + ExecReturn(ExecReturnValue), +} + +type PreExecResult<'a, E> = Result, ExecError>; + +impl WasmBlob { + // Sync this frame's gas meter with the engine's one. + fn process_result>( + mut store: Store>, + result: Result<(), wasmi::Error>, + ) -> ExecResult { + let engine_consumed_total = store.fuel_consumed().expect("Fuel metering is enabled; qed"); + let gas_meter = store.data_mut().ext().gas_meter_mut(); + let _ = gas_meter.sync_from_executor(engine_consumed_total)?; + store.into_data().to_execution_result(result) } - fn execute>( + #[cfg(feature = "runtime-benchmarks")] + pub fn bench_prepare_call>( self, - ext: &mut E, - function: &ExportedFunction, + ext: &'static mut E, input_data: Vec, - ) -> ExecResult { + ) -> (Func, Store>) { + use InstanceOrExecReturn::*; + match Self::prepare_execute(self, Runtime::new(ext, input_data), &ExportedFunction::Call) + .expect("Benchmark should provide valid module") + { + Instance((func, store)) => (func, store), + ExecReturn(_) => panic!("Expected Instance"), + } + } + + fn prepare_execute<'a, E: Ext>( + self, + runtime: Runtime<'a, E>, + function: &'a ExportedFunction, + ) -> PreExecResult<'a, E> { let code = self.code.as_slice(); // Instantiate the Wasm module to the engine. - let runtime = Runtime::new(ext, input_data); let schedule = ::Schedule::get(); let (mut store, memory, instance) = Self::instantiate::( code, @@ -389,17 +412,8 @@ impl Executable for WasmBlob { .add_fuel(fuel_limit) .expect("We've set up engine to fuel consuming mode; qed"); - // Sync this frame's gas meter with the engine's one. - let process_result = |mut store: Store>, result| { - let engine_consumed_total = - store.fuel_consumed().expect("Fuel metering is enabled; qed"); - let gas_meter = store.data_mut().ext().gas_meter_mut(); - let _ = gas_meter.sync_from_executor(engine_consumed_total)?; - store.into_data().to_execution_result(result) - }; - // Start function should already see the correct refcount in case it will be ever inspected. - if let &ExportedFunction::Constructor = function { + if let ExportedFunction::Constructor = function { E::increment_refcount(self.code_hash)?; } @@ -416,10 +430,37 @@ impl Executable for WasmBlob { Error::::CodeRejected })?; - let result = exported_func.call(&mut store, &[], &mut []); - process_result(store, result) + Ok(InstanceOrExecReturn::Instance((exported_func, store))) + }, + Err(err) => Self::process_result(store, Err(err)).map(InstanceOrExecReturn::ExecReturn), + } + } +} + +impl Executable for WasmBlob { + fn from_storage( + code_hash: CodeHash, + gas_meter: &mut GasMeter, + ) -> Result { + let code_info = >::get(code_hash).ok_or(Error::::CodeNotFound)?; + gas_meter.charge(CodeLoadToken(code_info.code_len))?; + let code = >::get(code_hash).ok_or(Error::::CodeNotFound)?; + Ok(Self { code, code_info, code_hash }) + } + + fn execute>( + self, + ext: &mut E, + function: &ExportedFunction, + input_data: Vec, + ) -> ExecResult { + use InstanceOrExecReturn::*; + match Self::prepare_execute(self, Runtime::new(ext, input_data), function)? { + Instance((func, mut store)) => { + let result = func.call(&mut store, &[], &mut []); + Self::process_result(store, result) }, - Err(err) => process_result(store, Err(err)), + ExecReturn(exec_return) => Ok(exec_return), } }