contracts: switch to wasmi gas metering (#14084)

* upgrade to wasmi 0.29

* prepare cleanup

* sync ref_time w engine from the stack frame

* proc_macro: sync gas in host funcs

save: compiles, only gas pushing left to macro

WIP proc macro

proc macro: done

* clean benchmarks & schedule: w_base = w_i64const

* scale gas values btw engine and gas meter

* (re)instrumentation & code_cache removed

* remove gas() host fn, continue clean-up

save

* address review comments

* move from CodeStorage&PrefabWasmModule to PristineCode&WasmBlob

* refactor: no reftime_limit&schedule passes, no CodeStorage

* bugs fixing

* fix tests: expected deposit amount

* fix prepare::tests

* update tests and fix bugs

tests::run_out_of_gas_engine, need 2 more

save: 2 bugs with gas syncs: 1 of 2 tests done

gas_syncs_no_overcharge bug fixed, test passes!

cleaned out debug prints

second bug is not a bug

disabled_chain_extension test fix (err msg)

tests run_out_of_fuel_host, chain_extension pass

all tests pass

* update docs

* bump wasmi 0.30.0

* benchmarks updated, tests pass

* refactoring

* s/OwnerInfo/CodeInfo/g;

* migration: draft, compiles

* migration: draft, runs

* migration: draft, runs (fixing)

* deposits repaid non pro rata

* deposits repaid pro rata

* better try-runtime output

* even better try-runtime output

* benchmark migration

* fix merge leftover

* add forgotten fixtures, fix docs

* address review comments

* ci fixes

* cleanup

* benchmarks::prepare to return DispatchError

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts

* store memory limits to CodeInfo

* ci: roll back weights

* ".git/.scripts/commands/bench-vm/bench-vm.sh" pallet dev pallet_contracts

* drive-by: update Readme and pallet rustdoc

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts

* use wasmi 0.29

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts

* use wasmi 0.30 again

* query memory limits from wasmi

* better migration types

* ci: pull weights from master

* refactoring

* ".git/.scripts/commands/bench-vm/bench-vm.sh" pallet dev pallet_contracts

* addressing review comments

* refactor

* address review comments

* optimize migration

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts

* another review round comments addressed

* ci fix one

* clippy fix

* ci fix two

---------

Co-authored-by: command-bot <>
This commit is contained in:
Sasha Gryaznov
2023-07-03 14:04:10 +03:00
committed by GitHub
parent e42768ea34
commit fda86dd501
23 changed files with 2786 additions and 4588 deletions
@@ -24,18 +24,15 @@
//! we define this simple definition of a contract that can be passed to `create_code` that
//! compiles it down into a `WasmModule` that can be used as a contract's code.
use crate::{Config, Determinism};
use crate::Config;
use frame_support::traits::Get;
use sp_runtime::traits::Hash;
use sp_std::{borrow::ToOwned, prelude::*};
use wasm_instrument::{
gas_metering,
parity_wasm::{
builder,
elements::{
self, BlockType, CustomSection, External, FuncBody, Instruction, Instructions, Module,
Section, ValueType,
},
use wasm_instrument::parity_wasm::{
builder,
elements::{
self, BlockType, CustomSection, External, FuncBody, Instruction, Instructions, Module,
Section, ValueType,
},
};
@@ -240,15 +237,9 @@ impl<T: Config> From<ModuleDefinition> for WasmModule<T> {
}
impl<T: Config> WasmModule<T> {
/// Uses the supplied wasm module and instruments it when requested.
pub fn instrumented(code: &[u8], inject_gas: bool) -> Self {
let module = {
let mut module = Module::from_bytes(code).unwrap();
if inject_gas {
module = inject_gas_metering::<T>(module);
}
module
};
/// Uses the supplied wasm module.
pub fn from_code(code: &[u8]) -> Self {
let module = Module::from_bytes(code).unwrap();
let limits = *module
.import_section()
.unwrap()
@@ -366,37 +357,13 @@ impl<T: Config> WasmModule<T> {
}
.into()
}
pub fn unary_instr(instr: Instruction, repeat: u32) -> Self {
use body::DynInstr::{RandomI64Repeated, Regular};
ModuleDefinition {
call_body: Some(body::repeated_dyn(
repeat,
vec![RandomI64Repeated(1), Regular(instr), Regular(Instruction::Drop)],
)),
..Default::default()
}
.into()
}
pub fn binary_instr(instr: Instruction, repeat: u32) -> Self {
use body::DynInstr::{RandomI64Repeated, Regular};
ModuleDefinition {
call_body: Some(body::repeated_dyn(
repeat,
vec![RandomI64Repeated(2), Regular(instr), Regular(Instruction::Drop)],
)),
..Default::default()
}
.into()
}
}
/// Mechanisms to generate a function body that can be used inside a `ModuleDefinition`.
pub mod body {
use super::*;
/// When generating contract code by repeating a wasm sequence, it's sometimes necessary
/// When generating contract code by repeating a Wasm sequence, it's sometimes necessary
/// to change those instructions on each repetition. The variants of this enum describe
/// various ways in which this can happen.
pub enum DynInstr {
@@ -405,31 +372,8 @@ pub mod body {
/// Insert a I32Const with incrementing value for each insertion.
/// (start_at, increment_by)
Counter(u32, u32),
/// Insert a I32Const with a random value in [low, high) not divisible by two.
/// (low, high)
RandomUnaligned(u32, u32),
/// Insert a I32Const with a random value in [low, high).
/// (low, high)
RandomI32(i32, i32),
/// Insert the specified amount of I32Const with a random value.
RandomI32Repeated(usize),
/// Insert the specified amount of I64Const with a random value.
RandomI64Repeated(usize),
/// Insert a GetLocal with a random offset in [low, high).
/// (low, high)
RandomGetLocal(u32, u32),
/// Insert a SetLocal with a random offset in [low, high).
/// (low, high)
RandomSetLocal(u32, u32),
/// Insert a TeeLocal with a random offset in [low, high).
/// (low, high)
RandomTeeLocal(u32, u32),
/// Insert a GetGlobal with a random offset in [low, high).
/// (low, high)
RandomGetGlobal(u32, u32),
/// Insert a SetGlobal with a random offset in [low, high).
/// (low, high)
RandomSetGlobal(u32, u32),
}
pub fn plain(instructions: Vec<Instruction>) -> FuncBody {
@@ -466,53 +410,16 @@ pub mod body {
*offset += *increment_by;
vec![Instruction::I32Const(current as i32)]
},
DynInstr::RandomUnaligned(low, high) => {
let unaligned = rng.gen_range(*low..*high) | 1;
vec![Instruction::I32Const(unaligned as i32)]
},
DynInstr::RandomI32(low, high) => {
vec![Instruction::I32Const(rng.gen_range(*low..*high))]
},
DynInstr::RandomI32Repeated(num) =>
(&mut rng).sample_iter(Standard).take(*num).map(Instruction::I32Const).collect(),
DynInstr::RandomI64Repeated(num) =>
(&mut rng).sample_iter(Standard).take(*num).map(Instruction::I64Const).collect(),
DynInstr::RandomGetLocal(low, high) => {
vec![Instruction::GetLocal(rng.gen_range(*low..*high))]
},
DynInstr::RandomSetLocal(low, high) => {
vec![Instruction::SetLocal(rng.gen_range(*low..*high))]
},
DynInstr::RandomTeeLocal(low, high) => {
vec![Instruction::TeeLocal(rng.gen_range(*low..*high))]
},
DynInstr::RandomGetGlobal(low, high) => {
vec![Instruction::GetGlobal(rng.gen_range(*low..*high))]
},
DynInstr::RandomSetGlobal(low, high) => {
vec![Instruction::SetGlobal(rng.gen_range(*low..*high))]
},
})
.chain(sp_std::iter::once(Instruction::End))
.collect();
FuncBody::new(Vec::new(), Instructions::new(body))
}
/// Replace the locals of the supplied `body` with `num` i64 locals.
pub fn inject_locals(body: &mut FuncBody, num: u32) {
use self::elements::Local;
*body.locals_mut() = vec![Local::new(num, ValueType::I64)];
}
}
/// The maximum amount of pages any contract is allowed to have according to the current `Schedule`.
pub fn max_pages<T: Config>() -> u32 {
T::Schedule::get().limits.memory_pages
}
fn inject_gas_metering<T: Config>(module: Module) -> Module {
let schedule = T::Schedule::get();
let gas_rules = schedule.rules(Determinism::Enforced);
let backend = gas_metering::host_function::Injector::new("seal0", "gas");
gas_metering::inject(module, backend, &gas_rules).unwrap()
}
+28 -843
View File
@@ -30,7 +30,7 @@ use self::{
};
use crate::{
exec::{AccountIdOf, Key},
migration::{v10, v11, v9, MigrationStep},
migration::{v10, v11, v12, v9, MigrationStep},
wasm::CallFlags,
Pallet as Contracts, *,
};
@@ -38,12 +38,9 @@ use codec::{Encode, MaxEncodedLen};
use frame_benchmarking::v1::{account, benchmarks, whitelisted_caller};
use frame_support::{pallet_prelude::StorageVersion, weights::Weight};
use frame_system::RawOrigin;
use sp_runtime::{
traits::{Bounded, Hash},
Perbill,
};
use sp_runtime::traits::{Bounded, Hash};
use sp_std::prelude::*;
use wasm_instrument::parity_wasm::elements::{BlockType, BrTableData, Instruction, ValueType};
use wasm_instrument::parity_wasm::elements::{BlockType, Instruction, ValueType};
/// How many runs we do per API benchmark.
///
@@ -162,16 +159,12 @@ where
/// Returns `true` iff all storage entries related to code storage exist.
fn code_exists(hash: &CodeHash<T>) -> bool {
<PristineCode<T>>::contains_key(hash) &&
<CodeStorage<T>>::contains_key(&hash) &&
<OwnerInfoOf<T>>::contains_key(&hash)
<PristineCode<T>>::contains_key(hash) && <CodeInfoOf<T>>::contains_key(&hash)
}
/// Returns `true` iff no storage entry related to code storage exist.
fn code_removed(hash: &CodeHash<T>) -> bool {
!<PristineCode<T>>::contains_key(hash) &&
!<CodeStorage<T>>::contains_key(&hash) &&
!<OwnerInfoOf<T>>::contains_key(&hash)
!<PristineCode<T>>::contains_key(hash) && !<CodeInfoOf<T>>::contains_key(&hash)
}
}
@@ -219,22 +212,7 @@ benchmarks! {
ContractInfo::<T>::process_deletion_queue_batch(Weight::MAX)
}
// This benchmarks the additional weight that is charged when a contract is executed the
// first time after a new schedule was deployed: For every new schedule a contract needs
// to re-run the instrumentation once.
#[pov_mode = Measured]
reinstrument {
let c in 0 .. Perbill::from_percent(49).mul_ceil(T::MaxCodeLen::get());
let WasmModule { code, hash, .. } = WasmModule::<T>::sized(c, Location::Call);
Contracts::<T>::store_code_raw(code, whitelisted_caller())?;
let schedule = T::Schedule::get();
let mut gas_meter = GasMeter::new(Weight::MAX);
let mut module = PrefabWasmModule::from_storage(hash, &schedule, &mut gas_meter)?;
}: {
Contracts::<T>::reinstrument_module(&mut module, &schedule)?;
}
// This benchmarks the v9 migration step. (update codeStorage)
// This benchmarks the v9 migration step (update codeStorage).
#[pov_mode = Measured]
v9_migration_step {
let c in 0 .. T::MaxCodeLen::get();
@@ -244,7 +222,7 @@ benchmarks! {
m.step();
}
// This benchmarks the v10 migration step. (use dedicated deposit_account)
// This benchmarks the v10 migration step (use dedicated deposit_account).
#[pov_mode = Measured]
v10_migration_step {
let contract = <Contract<T>>::with_caller(
@@ -257,7 +235,7 @@ benchmarks! {
m.step();
}
// This benchmarks the v11 migration step.
// This benchmarks the v11 migration step (Don't rely on reserved balances keeping an account alive).
#[pov_mode = Measured]
v11_migration_step {
let k in 0 .. 1024;
@@ -267,6 +245,18 @@ benchmarks! {
m.step();
}
// This benchmarks the v12 migration step (Move `OwnerInfo` to `CodeInfo`,
// add `determinism` field to the latter, clear `CodeStorage`
// and repay deposits).
#[pov_mode = Measured]
v12_migration_step {
let c in 0 .. T::MaxCodeLen::get();
v12::store_old_dummy_code::<T>(c as usize, account::<T::AccountId>("account", 0, 0));
let mut m = v12::Migration::<T>::default();
}: {
m.step();
}
// This benchmarks the weight of executing Migration::migrate to execute a noop migration.
#[pov_mode = Measured]
migration_noop {
@@ -348,14 +338,9 @@ benchmarks! {
// `c`: Size of the code in bytes.
// `i`: Size of the input in bytes.
// `s`: Size of the salt in bytes.
//
// # Note
//
// We cannot let `c` grow to the maximum code size because the code is not allowed
// to be larger than the maximum size **after instrumentation**.
#[pov_mode = Measured]
instantiate_with_code {
let c in 0 .. Perbill::from_percent(49).mul_ceil(T::MaxCodeLen::get());
let c in 0 .. T::MaxCodeLen::get();
let i in 0 .. code::max_pages::<T>() * 64 * 1024;
let s in 0 .. code::max_pages::<T>() * 64 * 1024;
let input = vec![42u8; i as usize];
@@ -445,14 +430,9 @@ benchmarks! {
// This constructs a contract that is maximal expensive to instrument.
// It creates a maximum number of metering blocks per byte.
// `c`: Size of the code in bytes.
//
// # Note
//
// We cannot let `c` grow to the maximum code size because the code is not allowed
// to be larger than the maximum size **after instrumentation**.
#[pov_mode = Measured]
upload_code {
let c in 0 .. Perbill::from_percent(49).mul_ceil(T::MaxCodeLen::get());
let c in 0 .. T::MaxCodeLen::get();
let caller = whitelisted_caller();
T::Currency::make_free_balance_be(&caller, caller_funding::<T>());
let WasmModule { code, hash, .. } = WasmModule::<T>::sized(c, Location::Call);
@@ -466,7 +446,7 @@ benchmarks! {
// Removing code does not depend on the size of the contract because all the information
// needed to verify the removal claim (refcount, owner) is stored in a separate storage
// item (`OwnerInfoOf`).
// item (`CodeInfoOf`).
#[pov_mode = Measured]
remove_code {
let caller = whitelisted_caller();
@@ -736,27 +716,6 @@ benchmarks! {
let origin = RawOrigin::Signed(instance.caller.clone());
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
#[pov_mode = Measured]
seal_gas {
let r in 0 .. API_BENCHMARK_RUNS;
let code = WasmModule::<T>::from(ModuleDefinition {
imported_functions: vec![ImportedFunction {
module: "seal0",
name: "gas",
params: vec![ValueType::I64],
return_type: None,
}],
call_body: Some(body::repeated(r, &[
Instruction::I64Const(42),
Instruction::Call(0),
])),
.. Default::default()
});
let instance = Contract::<T>::new(code, vec![])?;
let origin = RawOrigin::Signed(instance.caller.clone());
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
#[pov_mode = Measured]
seal_input {
let r in 0 .. API_BENCHMARK_RUNS;
@@ -1051,7 +1010,7 @@ benchmarks! {
// or maximum allowed debug buffer size, whichever is less.
let i in 0 .. (T::Schedule::get().limits.memory_pages * 64 * 1024).min(T::MaxDebugBufferLen::get());
// We benchmark versus messages containing printable ASCII codes.
// About 1Kb goes to the instrumented contract code instructions,
// About 1Kb goes to the contract code instructions,
// whereas all the space left we use for the initialization of the debug messages data.
let message = (0 .. T::MaxCodeLen::get() - 1024).zip((32..127).cycle()).map(|i| i.1).collect::<Vec<_>>();
let code = WasmModule::<T>::from(ModuleDefinition {
@@ -2445,15 +2404,10 @@ benchmarks! {
}: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![])
// We make the assumption that pushing a constant and dropping a value takes roughly
// the same amount of time. We follow that `t.load` and `drop` both have the weight
// of this benchmark / 2. We need to make this assumption because there is no way
// to measure them on their own using a valid wasm module. We need their individual
// values to derive the weight of individual instructions (by subtraction) from
// benchmarks that include those for parameter pushing and return type dropping.
// We call the weight of `t.load` and `drop`: `w_param`.
// the same amount of time. We call this weight `w_base`.
// The weight that would result from the respective benchmark we call: `w_bench`.
//
// w_i{32,64}const = w_drop = w_bench / 2
// w_base = w_i{32,64}const = w_drop = w_bench / 2
#[pov_mode = Ignored]
instr_i64const {
let r in 0 .. INSTR_BENCHMARK_RUNS;
@@ -2468,765 +2422,6 @@ benchmarks! {
sbox.invoke();
}
// w_i{32,64}load = w_bench - 2 * w_param
#[pov_mode = Ignored]
instr_i64load {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
call_body: Some(body::repeated_dyn(r, vec![
RandomUnaligned(0, code::max_pages::<T>() * 64 * 1024 - 8),
Regular(Instruction::I64Load(3, 0)),
Regular(Instruction::Drop),
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_i{32,64}store{...} = w_bench - 2 * w_param
#[pov_mode = Ignored]
instr_i64store {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
call_body: Some(body::repeated_dyn(r, vec![
RandomUnaligned(0, code::max_pages::<T>() * 64 * 1024 - 8),
RandomI64Repeated(1),
Regular(Instruction::I64Store(3, 0)),
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_select = w_bench - 4 * w_param
#[pov_mode = Ignored]
instr_select {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(body::repeated_dyn(r, vec![
RandomI64Repeated(1),
RandomI64Repeated(1),
RandomI32(0, 2),
Regular(Instruction::Select),
Regular(Instruction::Drop),
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_if = w_bench - 3 * w_param
#[pov_mode = Ignored]
instr_if {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(body::repeated_dyn(r, vec![
RandomI32(0, 2),
Regular(Instruction::If(BlockType::Value(ValueType::I64))),
RandomI64Repeated(1),
Regular(Instruction::Else),
RandomI64Repeated(1),
Regular(Instruction::End),
Regular(Instruction::Drop),
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_br = w_bench - 2 * w_param
// Block instructions are not counted.
#[pov_mode = Ignored]
instr_br {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(body::repeated_dyn(r, vec![
Regular(Instruction::Block(BlockType::NoResult)),
Regular(Instruction::Block(BlockType::NoResult)),
Regular(Instruction::Block(BlockType::NoResult)),
Regular(Instruction::Br(1)),
RandomI64Repeated(1),
Regular(Instruction::Drop),
Regular(Instruction::End),
RandomI64Repeated(1),
Regular(Instruction::Drop),
Regular(Instruction::End),
RandomI64Repeated(1),
Regular(Instruction::Drop),
Regular(Instruction::End),
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_br_if = w_bench - 3 * w_param
// Block instructions are not counted.
#[pov_mode = Ignored]
instr_br_if {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(body::repeated_dyn(r, vec![
Regular(Instruction::Block(BlockType::NoResult)),
Regular(Instruction::Block(BlockType::NoResult)),
Regular(Instruction::Block(BlockType::NoResult)),
Regular(Instruction::I32Const(1)),
Regular(Instruction::BrIf(1)),
RandomI64Repeated(1),
Regular(Instruction::Drop),
Regular(Instruction::End),
RandomI64Repeated(1),
Regular(Instruction::Drop),
Regular(Instruction::End),
RandomI64Repeated(1),
Regular(Instruction::Drop),
Regular(Instruction::End),
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_br_table = w_bench - 3 * w_param
// Block instructions are not counted.
#[pov_mode = Ignored]
instr_br_table {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let table = Box::new(BrTableData {
table: Box::new([1, 1, 1]),
default: 1,
});
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(body::repeated_dyn(r, vec![
Regular(Instruction::Block(BlockType::NoResult)),
Regular(Instruction::Block(BlockType::NoResult)),
Regular(Instruction::Block(BlockType::NoResult)),
RandomI32(0, 4),
Regular(Instruction::BrTable(table)),
RandomI64Repeated(1),
Regular(Instruction::Drop),
Regular(Instruction::End),
RandomI64Repeated(1),
Regular(Instruction::Drop),
Regular(Instruction::End),
RandomI64Repeated(1),
Regular(Instruction::Drop),
Regular(Instruction::End),
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_br_table_per_entry = w_bench
#[pov_mode = Ignored]
instr_br_table_per_entry {
let e in 1 .. T::Schedule::get().limits.br_table_size;
let entry: Vec<u32> = [0, 1].iter()
.cloned()
.cycle()
.take((e / 2) as usize).collect();
let table = Box::new(BrTableData {
table: entry.into_boxed_slice(),
default: 0,
});
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(body::plain(vec![
Instruction::Block(BlockType::NoResult),
Instruction::Block(BlockType::NoResult),
Instruction::Block(BlockType::NoResult),
Instruction::I32Const((e / 2) as i32),
Instruction::BrTable(table),
Instruction::I64Const(42),
Instruction::Drop,
Instruction::End,
Instruction::I64Const(42),
Instruction::Drop,
Instruction::End,
Instruction::I64Const(42),
Instruction::Drop,
Instruction::End,
Instruction::End,
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_call = w_bench - 2 * w_param
#[pov_mode = Ignored]
instr_call {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
// We need to make use of the stack here in order to trigger stack height
// instrumentation.
aux_body: Some(body::plain(vec![
Instruction::I64Const(42),
Instruction::Drop,
Instruction::End,
])),
call_body: Some(body::repeated(r, &[
Instruction::Call(2), // call aux
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_call_indrect = w_bench - 3 * w_param
#[pov_mode = Ignored]
instr_call_indirect {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let num_elements = T::Schedule::get().limits.table_size;
use self::code::TableSegment;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
// We need to make use of the stack here in order to trigger stack height
// instrumentation.
aux_body: Some(body::plain(vec![
Instruction::I64Const(42),
Instruction::Drop,
Instruction::End,
])),
call_body: Some(body::repeated_dyn(r, vec![
RandomI32(0, num_elements as i32),
Regular(Instruction::CallIndirect(0, 0)), // we only have one sig: 0
])),
table: Some(TableSegment {
num_elements,
function_index: 2, // aux
}),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_per_local = w_bench
#[pov_mode = Ignored]
instr_call_per_local {
let l in 0 .. T::Schedule::get().limits.locals;
let mut aux_body = body::plain(vec![
Instruction::End,
]);
body::inject_locals(&mut aux_body, l);
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
aux_body: Some(aux_body),
call_body: Some(body::plain(vec![
Instruction::Call(2), // call aux
Instruction::End,
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_local_get = w_bench - 1 * w_param
#[pov_mode = Ignored]
instr_local_get {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let max_locals = T::Schedule::get().limits.locals;
let mut call_body = body::repeated_dyn(r, vec![
RandomGetLocal(0, max_locals),
Regular(Instruction::Drop),
]);
body::inject_locals(&mut call_body, max_locals);
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(call_body),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_local_set = w_bench - 1 * w_param
#[pov_mode = Ignored]
instr_local_set {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let max_locals = T::Schedule::get().limits.locals;
let mut call_body = body::repeated_dyn(r, vec![
RandomI64Repeated(1),
RandomSetLocal(0, max_locals),
]);
body::inject_locals(&mut call_body, max_locals);
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(call_body),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_local_tee = w_bench - 2 * w_param
#[pov_mode = Ignored]
instr_local_tee {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let max_locals = T::Schedule::get().limits.locals;
let mut call_body = body::repeated_dyn(r, vec![
RandomI64Repeated(1),
RandomTeeLocal(0, max_locals),
Regular(Instruction::Drop),
]);
body::inject_locals(&mut call_body, max_locals);
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(call_body),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_global_get = w_bench - 1 * w_param
#[pov_mode = Ignored]
instr_global_get {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let max_globals = T::Schedule::get().limits.globals;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(body::repeated_dyn(r, vec![
RandomGetGlobal(0, max_globals),
Regular(Instruction::Drop),
])),
num_globals: max_globals,
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_global_set = w_bench - 1 * w_param
#[pov_mode = Ignored]
instr_global_set {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let max_globals = T::Schedule::get().limits.globals;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(body::repeated_dyn(r, vec![
RandomI64Repeated(1),
RandomSetGlobal(0, max_globals),
])),
num_globals: max_globals,
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_memory_get = w_bench - 1 * w_param
#[pov_mode = Ignored]
instr_memory_current {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
call_body: Some(body::repeated(r, &[
Instruction::CurrentMemory(0),
Instruction::Drop
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
// w_memory_grow = w_bench - 2 * w_param
// We can only allow allocate as much memory as it is allowed in a contract.
// Therefore the repeat count is limited by the maximum memory any contract can have.
// Using a contract with more memory will skew the benchmark because the runtime of grow
// depends on how much memory is already allocated.
#[pov_mode = Ignored]
instr_memory_grow {
let r in 0 .. ImportedMemory::max::<T>().max_pages;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory {
min_pages: 0,
max_pages: ImportedMemory::max::<T>().max_pages,
}),
call_body: Some(body::repeated(r, &[
Instruction::I32Const(1),
Instruction::GrowMemory(0),
Instruction::Drop,
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
// Unary numeric instructions.
// All use w = w_bench - 2 * w_param.
#[pov_mode = Ignored]
instr_i64clz {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::unary_instr(
Instruction::I64Clz,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64ctz {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::unary_instr(
Instruction::I64Ctz,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64popcnt {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::unary_instr(
Instruction::I64Popcnt,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64eqz {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::unary_instr(
Instruction::I64Eqz,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64extendsi32 {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(body::repeated_dyn(r, vec![
RandomI32Repeated(1),
Regular(Instruction::I64ExtendSI32),
Regular(Instruction::Drop),
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64extendui32 {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(body::repeated_dyn(r, vec![
RandomI32Repeated(1),
Regular(Instruction::I64ExtendUI32),
Regular(Instruction::Drop),
])),
.. Default::default()
}));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i32wrapi64 {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::unary_instr(
Instruction::I32WrapI64,
r,
));
}: {
sbox.invoke();
}
// Binary numeric instructions.
// All use w = w_bench - 3 * w_param.
#[pov_mode = Ignored]
instr_i64eq {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64Eq,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64ne {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64Ne,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64lts {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64LtS,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64ltu {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64LtU,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64gts {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64GtS,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64gtu {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64GtU,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64les {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64LeS,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64leu {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64LeU,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64ges {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64GeS,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64geu {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64GeU,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64add {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64Add,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64sub {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64Sub,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64mul {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64Mul,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64divs {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64DivS,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64divu {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64DivU,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64rems {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64RemS,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64remu {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64RemU,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64and {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64And,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64or {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64Or,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64xor {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64Xor,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64shl {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64Shl,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64shrs {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64ShrS,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64shru {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64ShrU,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64rotl {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64Rotl,
r,
));
}: {
sbox.invoke();
}
#[pov_mode = Ignored]
instr_i64rotr {
let r in 0 .. INSTR_BENCHMARK_RUNS;
let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
Instruction::I64Rotr,
r,
));
}: {
sbox.invoke();
}
// This is no benchmark. It merely exist to have an easy way to pretty print the currently
// configured `Schedule` during benchmark development.
// It can be outputted using the following command:
@@ -3250,21 +2445,16 @@ benchmarks! {
}: {}
// Execute one erc20 transfer using the ink! erc20 example contract.
//
// `g` is used to enable gas instrumentation to compare the performance impact of
// that instrumentation at runtime.
#[extra]
#[pov_mode = Measured]
ink_erc20_transfer {
let g in 0 .. 1;
let gas_metering = g != 0;
let code = load_benchmark!("ink_erc20");
let data = {
let new: ([u8; 4], BalanceOf<T>) = ([0x9b, 0xae, 0x9d, 0x5e], 1000u32.into());
new.encode()
};
let instance = Contract::<T>::new(
WasmModule::instrumented(code, gas_metering), data,
WasmModule::from_code(code), data,
)?;
let data = {
let transfer: ([u8; 4], AccountIdOf<T>, BalanceOf<T>) = (
@@ -3290,14 +2480,9 @@ benchmarks! {
}
// Execute one erc20 transfer using the open zeppelin erc20 contract compiled with solang.
//
// `g` is used to enable gas instrumentation to compare the performance impact of
// that instrumentation at runtime.
#[extra]
#[pov_mode = Measured]
solang_erc20_transfer {
let g in 0 .. 1;
let gas_metering = g != 0;
let code = include_bytes!("../../benchmarks/solang_erc20.wasm");
let caller = account::<T::AccountId>("instantiator", 0, 0);
let mut balance = [0u8; 32];
@@ -3313,7 +2498,7 @@ benchmarks! {
new.encode()
};
let instance = Contract::<T>::with_caller(
caller, WasmModule::instrumented(code, gas_metering), data,
caller, WasmModule::from_code(code), data,
)?;
balance[0] = 1;
let data = {
@@ -15,13 +15,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
/// ! For instruction benchmarking we do no instantiate a full contract but merely the
/// ! sandbox to execute the wasm code. This is because we do not need the full
/// ! For instruction benchmarking we do not instantiate a full contract but merely the
/// ! sandbox to execute the Wasm code. This is because we do not need the full
/// ! environment that provides the seal interface as imported functions.
use super::{code::WasmModule, Config};
use crate::wasm::{
AllowDeprecatedInterface, AllowUnstableInterface, Environment, PrefabWasmModule,
};
use crate::wasm::{AllowDeprecatedInterface, AllowUnstableInterface, Environment, WasmBlob};
use sp_core::Get;
use wasmi::{errors::LinkerError, Func, Linker, StackLimits, Store};
/// Minimal execution environment without any imported functions.
@@ -38,23 +37,24 @@ impl Sandbox {
}
impl<T: Config> From<&WasmModule<T>> for Sandbox {
/// Creates an instance from the supplied module and supplies as much memory
/// to the instance as the module declares as imported.
/// Creates an instance from the supplied module.
/// Sets the execution engine fuel level to `u64::MAX`.
fn from(module: &WasmModule<T>) -> Self {
let memory = module
.memory
.as_ref()
.map(|mem| (mem.min_pages, mem.max_pages))
.unwrap_or((0, 0));
let (store, _memory, instance) = PrefabWasmModule::<T>::instantiate::<EmptyEnv, _>(
let (mut store, _memory, instance) = WasmBlob::<T>::instantiate::<EmptyEnv, _>(
&module.code,
(),
memory,
&<T>::Schedule::get(),
StackLimits::default(),
// We are testing with an empty environment anyways
AllowDeprecatedInterface::No,
)
.expect("Failed to create benchmarking Sandbox instance");
// Set fuel for wasmi execution.
store
.add_fuel(u64::MAX)
.expect("We've set up engine to fuel consuming mode; qed");
let entry_point = instance.get_export(&store, "call").unwrap().into_func().unwrap();
Self { entry_point, store }
}