mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-19 00:41:03 +00:00
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:
@@ -18,7 +18,7 @@
|
||||
//! This module contains the cost schedule and supporting code that constructs a
|
||||
//! sane default schedule from a `WeightInfo` implementation.
|
||||
|
||||
use crate::{wasm::Determinism, weights::WeightInfo, Config};
|
||||
use crate::{weights::WeightInfo, Config};
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::{weights::Weight, DefaultNoBound};
|
||||
@@ -28,7 +28,6 @@ use scale_info::TypeInfo;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_std::marker::PhantomData;
|
||||
use wasm_instrument::{gas_metering, parity_wasm::elements};
|
||||
|
||||
/// Definition of the cost schedule and other parameterizations for the wasm vm.
|
||||
///
|
||||
@@ -50,18 +49,12 @@ use wasm_instrument::{gas_metering, parity_wasm::elements};
|
||||
/// .. Default::default()
|
||||
/// },
|
||||
/// instruction_weights: InstructionWeights {
|
||||
/// version: 5,
|
||||
/// .. Default::default()
|
||||
/// },
|
||||
/// .. Default::default()
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Please make sure to bump the [`InstructionWeights::version`] whenever substantial
|
||||
/// changes are made to its values.
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "std", serde(bound(serialize = "", deserialize = "")))]
|
||||
#[derive(Clone, Encode, Decode, PartialEq, Eq, ScheduleDebug, DefaultNoBound, TypeInfo)]
|
||||
@@ -78,12 +71,6 @@ pub struct Schedule<T: Config> {
|
||||
}
|
||||
|
||||
/// Describes the upper limits on various metrics.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The values in this struct should never be decreased. The reason is that decreasing those
|
||||
/// values will break existing contracts which are above the new limits when a
|
||||
/// re-instrumentation is triggered.
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)]
|
||||
pub struct Limits {
|
||||
@@ -140,100 +127,15 @@ impl Limits {
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes the weight for all categories of supported wasm instructions.
|
||||
///
|
||||
/// There there is one field for each wasm instruction that describes the weight to
|
||||
/// execute one instruction of that name. There are a few exceptions:
|
||||
///
|
||||
/// 1. If there is a i64 and a i32 variant of an instruction we use the weight of the former for
|
||||
/// both.
|
||||
/// 2. The following instructions are free of charge because they merely structure the wasm module
|
||||
/// and cannot be spammed without making the module invalid (and rejected): End, Unreachable,
|
||||
/// Return, Else
|
||||
/// 3. The following instructions cannot be benchmarked because they are removed by any real world
|
||||
/// execution engine as a preprocessing step and therefore don't yield a meaningful benchmark
|
||||
/// result. However, in contrast to the instructions mentioned in 2. they can be spammed. We
|
||||
/// price them with the same weight as the "default" instruction (i64.const): Block, Loop, Nop
|
||||
/// 4. We price both i64.const and drop as InstructionWeights.i64const / 2. The reason for that is
|
||||
/// that we cannot benchmark either of them on its own but we need their individual values to
|
||||
/// derive (by subtraction) the weight of all other instructions that use them as supporting
|
||||
/// instructions. Supporting means mainly pushing arguments and dropping return values in order
|
||||
/// to maintain a valid module.
|
||||
/// Gas metering of Wasm executed instructions is being done on the engine side.
|
||||
/// This struct holds a reference value used to gas units scaling between host and engine.
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
#[derive(Clone, Encode, Decode, PartialEq, Eq, ScheduleDebug, TypeInfo)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct InstructionWeights<T: Config> {
|
||||
/// Version of the instruction weights.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Should be incremented whenever any instruction weight is changed. The
|
||||
/// reason is that changes to instruction weights require a re-instrumentation
|
||||
/// in order to apply the changes to an already deployed code. The re-instrumentation
|
||||
/// is triggered by comparing the version of the current schedule with the version the code was
|
||||
/// instrumented with. Changes usually happen when pallet_contracts is re-benchmarked.
|
||||
///
|
||||
/// Changes to other parts of the schedule should not increment the version in
|
||||
/// order to avoid unnecessary re-instrumentations.
|
||||
pub version: u32,
|
||||
/// Weight to be used for instructions which don't have benchmarks assigned.
|
||||
///
|
||||
/// This weight is used whenever a code is uploaded with [`Determinism::Relaxed`]
|
||||
/// and an instruction (usually a float instruction) is encountered. This weight is **not**
|
||||
/// used if a contract is uploaded with [`Determinism::Enforced`]. If this field is set to
|
||||
/// `0` (the default) only deterministic codes are allowed to be uploaded.
|
||||
pub fallback: u32,
|
||||
pub i64const: u32,
|
||||
pub i64load: u32,
|
||||
pub i64store: u32,
|
||||
pub select: u32,
|
||||
pub r#if: u32,
|
||||
pub br: u32,
|
||||
pub br_if: u32,
|
||||
pub br_table: u32,
|
||||
pub br_table_per_entry: u32,
|
||||
pub call: u32,
|
||||
pub call_indirect: u32,
|
||||
pub call_per_local: u32,
|
||||
pub local_get: u32,
|
||||
pub local_set: u32,
|
||||
pub local_tee: u32,
|
||||
pub global_get: u32,
|
||||
pub global_set: u32,
|
||||
pub memory_current: u32,
|
||||
pub memory_grow: u32,
|
||||
pub i64clz: u32,
|
||||
pub i64ctz: u32,
|
||||
pub i64popcnt: u32,
|
||||
pub i64eqz: u32,
|
||||
pub i64extendsi32: u32,
|
||||
pub i64extendui32: u32,
|
||||
pub i32wrapi64: u32,
|
||||
pub i64eq: u32,
|
||||
pub i64ne: u32,
|
||||
pub i64lts: u32,
|
||||
pub i64ltu: u32,
|
||||
pub i64gts: u32,
|
||||
pub i64gtu: u32,
|
||||
pub i64les: u32,
|
||||
pub i64leu: u32,
|
||||
pub i64ges: u32,
|
||||
pub i64geu: u32,
|
||||
pub i64add: u32,
|
||||
pub i64sub: u32,
|
||||
pub i64mul: u32,
|
||||
pub i64divs: u32,
|
||||
pub i64divu: u32,
|
||||
pub i64rems: u32,
|
||||
pub i64remu: u32,
|
||||
pub i64and: u32,
|
||||
pub i64or: u32,
|
||||
pub i64xor: u32,
|
||||
pub i64shl: u32,
|
||||
pub i64shrs: u32,
|
||||
pub i64shru: u32,
|
||||
pub i64rotl: u32,
|
||||
pub i64rotr: u32,
|
||||
/// Base instruction `ref_time` Weight.
|
||||
/// Should match to wasmi's `1` fuel (see <https://github.com/paritytech/wasmi/issues/701>).
|
||||
pub base: u32,
|
||||
/// The type parameter is used in the default implementation.
|
||||
#[codec(skip)]
|
||||
pub _phantom: PhantomData<T>,
|
||||
@@ -286,9 +188,6 @@ pub struct HostFnWeights<T: Config> {
|
||||
/// Weight of calling `seal_weight_to_fee`.
|
||||
pub weight_to_fee: Weight,
|
||||
|
||||
/// Weight of calling `gas`.
|
||||
pub gas: Weight,
|
||||
|
||||
/// Weight of calling `seal_input`.
|
||||
pub input: Weight,
|
||||
|
||||
@@ -491,63 +390,10 @@ impl Default for Limits {
|
||||
}
|
||||
|
||||
impl<T: Config> Default for InstructionWeights<T> {
|
||||
/// We price both `i64.const` and `drop` as `instr_i64const / 2`. The reason
|
||||
/// for that is that we cannot benchmark either of them on its own.
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
version: 4,
|
||||
fallback: 0,
|
||||
i64const: cost_instr!(instr_i64const, 1),
|
||||
i64load: cost_instr!(instr_i64load, 2),
|
||||
i64store: cost_instr!(instr_i64store, 2),
|
||||
select: cost_instr!(instr_select, 4),
|
||||
r#if: cost_instr!(instr_if, 3),
|
||||
br: cost_instr!(instr_br, 2),
|
||||
br_if: cost_instr!(instr_br_if, 3),
|
||||
br_table: cost_instr!(instr_br_table, 3),
|
||||
br_table_per_entry: cost_instr!(instr_br_table_per_entry, 0),
|
||||
call: cost_instr!(instr_call, 2),
|
||||
call_indirect: cost_instr!(instr_call_indirect, 3),
|
||||
call_per_local: cost_instr!(instr_call_per_local, 0),
|
||||
local_get: cost_instr!(instr_local_get, 1),
|
||||
local_set: cost_instr!(instr_local_set, 1),
|
||||
local_tee: cost_instr!(instr_local_tee, 2),
|
||||
global_get: cost_instr!(instr_global_get, 1),
|
||||
global_set: cost_instr!(instr_global_set, 1),
|
||||
memory_current: cost_instr!(instr_memory_current, 1),
|
||||
memory_grow: cost_instr!(instr_memory_grow, 1),
|
||||
i64clz: cost_instr!(instr_i64clz, 2),
|
||||
i64ctz: cost_instr!(instr_i64ctz, 2),
|
||||
i64popcnt: cost_instr!(instr_i64popcnt, 2),
|
||||
i64eqz: cost_instr!(instr_i64eqz, 2),
|
||||
i64extendsi32: cost_instr!(instr_i64extendsi32, 2),
|
||||
i64extendui32: cost_instr!(instr_i64extendui32, 2),
|
||||
i32wrapi64: cost_instr!(instr_i32wrapi64, 2),
|
||||
i64eq: cost_instr!(instr_i64eq, 3),
|
||||
i64ne: cost_instr!(instr_i64ne, 3),
|
||||
i64lts: cost_instr!(instr_i64lts, 3),
|
||||
i64ltu: cost_instr!(instr_i64ltu, 3),
|
||||
i64gts: cost_instr!(instr_i64gts, 3),
|
||||
i64gtu: cost_instr!(instr_i64gtu, 3),
|
||||
i64les: cost_instr!(instr_i64les, 3),
|
||||
i64leu: cost_instr!(instr_i64leu, 3),
|
||||
i64ges: cost_instr!(instr_i64ges, 3),
|
||||
i64geu: cost_instr!(instr_i64geu, 3),
|
||||
i64add: cost_instr!(instr_i64add, 3),
|
||||
i64sub: cost_instr!(instr_i64sub, 3),
|
||||
i64mul: cost_instr!(instr_i64mul, 3),
|
||||
i64divs: cost_instr!(instr_i64divs, 3),
|
||||
i64divu: cost_instr!(instr_i64divu, 3),
|
||||
i64rems: cost_instr!(instr_i64rems, 3),
|
||||
i64remu: cost_instr!(instr_i64remu, 3),
|
||||
i64and: cost_instr!(instr_i64and, 3),
|
||||
i64or: cost_instr!(instr_i64or, 3),
|
||||
i64xor: cost_instr!(instr_i64xor, 3),
|
||||
i64shl: cost_instr!(instr_i64shl, 3),
|
||||
i64shrs: cost_instr!(instr_i64shrs, 3),
|
||||
i64shru: cost_instr!(instr_i64shru, 3),
|
||||
i64rotl: cost_instr!(instr_i64rotl, 3),
|
||||
i64rotr: cost_instr!(instr_i64rotr, 3),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
Self { base: cost_instr!(instr_i64const, 1), _phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -568,12 +414,6 @@ impl<T: Config> Default for HostFnWeights<T> {
|
||||
block_number: cost!(seal_block_number),
|
||||
now: cost!(seal_now),
|
||||
weight_to_fee: cost!(seal_weight_to_fee),
|
||||
// Manually remove proof size from basic block cost.
|
||||
//
|
||||
// Due to imperfect benchmarking some host functions incur a small
|
||||
// amount of proof size. Usually this is ok. However, charging a basic block is such
|
||||
// a frequent operation that this would be a vast overestimation.
|
||||
gas: cost!(seal_gas).set_proof_size(0),
|
||||
input: cost!(seal_input),
|
||||
input_per_byte: cost!(seal_input_per_byte),
|
||||
r#return: cost!(seal_return),
|
||||
@@ -641,113 +481,6 @@ impl<T: Config> Default for HostFnWeights<T> {
|
||||
}
|
||||
}
|
||||
|
||||
struct ScheduleRules<'a, T: Config> {
|
||||
schedule: &'a Schedule<T>,
|
||||
determinism: Determinism,
|
||||
}
|
||||
|
||||
impl<T: Config> Schedule<T> {
|
||||
pub(crate) fn rules(&self, determinism: Determinism) -> impl gas_metering::Rules + '_ {
|
||||
ScheduleRules { schedule: self, determinism }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Config> gas_metering::Rules for ScheduleRules<'a, T> {
|
||||
fn instruction_cost(&self, instruction: &elements::Instruction) -> Option<u32> {
|
||||
use self::elements::Instruction::*;
|
||||
let w = &self.schedule.instruction_weights;
|
||||
|
||||
let weight = match *instruction {
|
||||
End | Unreachable | Return | Else => 0,
|
||||
I32Const(_) | I64Const(_) | Block(_) | Loop(_) | Nop | Drop => w.i64const,
|
||||
I32Load(_, _) |
|
||||
I32Load8S(_, _) |
|
||||
I32Load8U(_, _) |
|
||||
I32Load16S(_, _) |
|
||||
I32Load16U(_, _) |
|
||||
I64Load(_, _) |
|
||||
I64Load8S(_, _) |
|
||||
I64Load8U(_, _) |
|
||||
I64Load16S(_, _) |
|
||||
I64Load16U(_, _) |
|
||||
I64Load32S(_, _) |
|
||||
I64Load32U(_, _) => w.i64load,
|
||||
I32Store(_, _) |
|
||||
I32Store8(_, _) |
|
||||
I32Store16(_, _) |
|
||||
I64Store(_, _) |
|
||||
I64Store8(_, _) |
|
||||
I64Store16(_, _) |
|
||||
I64Store32(_, _) => w.i64store,
|
||||
Select => w.select,
|
||||
If(_) => w.r#if,
|
||||
Br(_) => w.br,
|
||||
BrIf(_) => w.br_if,
|
||||
Call(_) => w.call,
|
||||
GetLocal(_) => w.local_get,
|
||||
SetLocal(_) => w.local_set,
|
||||
TeeLocal(_) => w.local_tee,
|
||||
GetGlobal(_) => w.global_get,
|
||||
SetGlobal(_) => w.global_set,
|
||||
CurrentMemory(_) => w.memory_current,
|
||||
GrowMemory(_) => w.memory_grow,
|
||||
CallIndirect(_, _) => w.call_indirect,
|
||||
BrTable(ref data) => w
|
||||
.br_table
|
||||
.saturating_add(w.br_table_per_entry.saturating_mul(data.table.len() as u32)),
|
||||
I32Clz | I64Clz => w.i64clz,
|
||||
I32Ctz | I64Ctz => w.i64ctz,
|
||||
I32Popcnt | I64Popcnt => w.i64popcnt,
|
||||
I32Eqz | I64Eqz => w.i64eqz,
|
||||
I64ExtendSI32 => w.i64extendsi32,
|
||||
I64ExtendUI32 => w.i64extendui32,
|
||||
I32WrapI64 => w.i32wrapi64,
|
||||
I32Eq | I64Eq => w.i64eq,
|
||||
I32Ne | I64Ne => w.i64ne,
|
||||
I32LtS | I64LtS => w.i64lts,
|
||||
I32LtU | I64LtU => w.i64ltu,
|
||||
I32GtS | I64GtS => w.i64gts,
|
||||
I32GtU | I64GtU => w.i64gtu,
|
||||
I32LeS | I64LeS => w.i64les,
|
||||
I32LeU | I64LeU => w.i64leu,
|
||||
I32GeS | I64GeS => w.i64ges,
|
||||
I32GeU | I64GeU => w.i64geu,
|
||||
I32Add | I64Add => w.i64add,
|
||||
I32Sub | I64Sub => w.i64sub,
|
||||
I32Mul | I64Mul => w.i64mul,
|
||||
I32DivS | I64DivS => w.i64divs,
|
||||
I32DivU | I64DivU => w.i64divu,
|
||||
I32RemS | I64RemS => w.i64rems,
|
||||
I32RemU | I64RemU => w.i64remu,
|
||||
I32And | I64And => w.i64and,
|
||||
I32Or | I64Or => w.i64or,
|
||||
I32Xor | I64Xor => w.i64xor,
|
||||
I32Shl | I64Shl => w.i64shl,
|
||||
I32ShrS | I64ShrS => w.i64shrs,
|
||||
I32ShrU | I64ShrU => w.i64shru,
|
||||
I32Rotl | I64Rotl => w.i64rotl,
|
||||
I32Rotr | I64Rotr => w.i64rotr,
|
||||
|
||||
// Returning None makes the gas instrumentation fail which we intend for
|
||||
// unsupported or unknown instructions. Offchain we might allow indeterminism and hence
|
||||
// use the fallback weight for those instructions.
|
||||
_ if matches!(self.determinism, Determinism::Relaxed) && w.fallback > 0 => w.fallback,
|
||||
_ => return None,
|
||||
};
|
||||
Some(weight)
|
||||
}
|
||||
|
||||
fn memory_grow_cost(&self) -> gas_metering::MemoryGrowCost {
|
||||
// We benchmarked the memory.grow instruction with the maximum allowed pages.
|
||||
// The cost for growing is therefore already included in the instruction cost.
|
||||
gas_metering::MemoryGrowCost::Free
|
||||
}
|
||||
|
||||
fn call_per_local_cost(&self) -> u32 {
|
||||
self.schedule.instruction_weights.call_per_local
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user