Skip to content

Commit

Permalink
Enable benchmarking with prepared module
Browse files Browse the repository at this point in the history
  • Loading branch information
pgherveou committed Mar 31, 2024
1 parent 78b1cab commit fb8d6e2
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 33 deletions.
68 changes: 68 additions & 0 deletions substrate/frame/contracts/src/benchmarking/host_sandbox.rs
Original file line number Diff line number Diff line change
@@ -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<T>>;

/// A sandbox used for benchmarking host functions.
pub struct Sandbox<T: Config> {
caller: T::AccountId,
dest: T::AccountId,
gas_meter: GasMeter<T>,
storage_meter: Meter<T>,
schedule: Schedule<T>,
}

impl<T> Sandbox<T>
where
T: Config + pallet_balances::Config,
<BalanceOf<T> as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode,
{
/// Create a new sandbox for the given module.
pub fn new(module: WasmModule<T>) -> &'static mut Self {
let instance = Contract::<T>::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<u8>,
) -> (Func, Store<Runtime<StackExt<'static, T>>>) {
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)
}
}
15 changes: 9 additions & 6 deletions substrate/frame/contracts/src/benchmarking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#![cfg(feature = "runtime-benchmarks")]

mod code;
mod host_sandbox;
mod sandbox;
use self::{
code::{
Expand Down Expand Up @@ -796,12 +797,14 @@ mod benchmarks {
}

#[benchmark(pov_mode = Measured)]
fn seal_now(r: Linear<0, API_BENCHMARK_RUNS>) -> Result<(), BenchmarkError> {
let instance = Contract::<T>::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::<T>::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)]
Expand Down
24 changes: 24 additions & 0 deletions substrate/frame/contracts/src/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>,
gas_meter: &'a mut GasMeter<T>,
storage_meter: &'a mut storage::meter::Meter<T>,
schedule: &'a Schedule<T>,
value: BalanceOf<T>,
debug_message: Option<&'a mut DebugBufferVec<T>>,
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<T, E>,
Expand Down
95 changes: 68 additions & 27 deletions substrate/frame/contracts/src/wasm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,26 +337,49 @@ impl<T: Config> CodeInfo<T> {
}
}

impl<T: Config> Executable<T> for WasmBlob<T> {
fn from_storage(
code_hash: CodeHash<T>,
gas_meter: &mut GasMeter<T>,
) -> Result<Self, DispatchError> {
let code_info = <CodeInfoOf<T>>::get(code_hash).ok_or(Error::<T>::CodeNotFound)?;
gas_meter.charge(CodeLoadToken(code_info.code_len))?;
let code = <PristineCode<T>>::get(code_hash).ok_or(Error::<T>::CodeNotFound)?;
Ok(Self { code, code_info, code_hash })
use crate::{ExecError, ExecReturnValue};
use wasmi::Func;
enum InstanceOrExecReturn<'a, E: Ext> {
Instance((Func, Store<Runtime<'a, E>>)),
ExecReturn(ExecReturnValue),
}

type PreExecResult<'a, E> = Result<InstanceOrExecReturn<'a, E>, ExecError>;

impl<T: Config> WasmBlob<T> {
// Sync this frame's gas meter with the engine's one.
fn process_result<E: Ext<T = T>>(
mut store: Store<Runtime<E>>,
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<E: Ext<T = T>>(
#[cfg(feature = "runtime-benchmarks")]
pub fn bench_prepare_call<E: Ext<T = T>>(
self,
ext: &mut E,
function: &ExportedFunction,
ext: &'static mut E,
input_data: Vec<u8>,
) -> ExecResult {
) -> (Func, Store<Runtime<E>>) {
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<T = T>>(
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 = <T>::Schedule::get();
let (mut store, memory, instance) = Self::instantiate::<crate::wasm::runtime::Env, _>(
code,
Expand Down Expand Up @@ -389,17 +412,8 @@ impl<T: Config> Executable<T> for WasmBlob<T> {
.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<Runtime<E>>, 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)?;
}

Expand All @@ -416,10 +430,37 @@ impl<T: Config> Executable<T> for WasmBlob<T> {
Error::<T>::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<T: Config> Executable<T> for WasmBlob<T> {
fn from_storage(
code_hash: CodeHash<T>,
gas_meter: &mut GasMeter<T>,
) -> Result<Self, DispatchError> {
let code_info = <CodeInfoOf<T>>::get(code_hash).ok_or(Error::<T>::CodeNotFound)?;
gas_meter.charge(CodeLoadToken(code_info.code_len))?;
let code = <PristineCode<T>>::get(code_hash).ok_or(Error::<T>::CodeNotFound)?;
Ok(Self { code, code_info, code_hash })
}

fn execute<E: Ext<T = T>>(
self,
ext: &mut E,
function: &ExportedFunction,
input_data: Vec<u8>,
) -> 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),
}
}

Expand Down

0 comments on commit fb8d6e2

Please sign in to comment.