git.delta.rocks / unique-network / refs/commits / e8fdadb89923

difftreelog

source

pallets/contracts/src/benchmarking/code.rs17.1 KiBsourcehistory
1// This file is part of Substrate.23// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd.4// SPDX-License-Identifier: Apache-2.056// Licensed under the Apache License, Version 2.0 (the "License");7// you may not use this file except in compliance with the License.8// You may obtain a copy of the License at9//10// 	http://www.apache.org/licenses/LICENSE-2.011//12// Unless required by applicable law or agreed to in writing, software13// distributed under the License is distributed on an "AS IS" BASIS,14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.15// See the License for the specific language governing permissions and16// limitations under the License.1718//! Functions to procedurally construct contract code used for benchmarking.19//!20//! In order to be able to benchmark events that are triggered by contract execution21//! (API calls into seal, individual instructions), we need to generate contracts that22//! perform those events. Because those contracts can get very big we cannot simply define23//! them as text (.wat) as this will be too slow and consume too much memory. Therefore24//! we define this simple definition of a contract that can be passed to `create_code` that25//! compiles it down into a `WasmModule` that can be used as a contract's code.2627use crate::{Config, CurrentSchedule};28use parity_wasm::elements::{29	Instruction, Instructions, FuncBody, ValueType, BlockType, Section, CustomSection,30};31use pwasm_utils::stack_height::inject_limiter;32use sp_core::crypto::UncheckedFrom;33use sp_runtime::traits::Hash;34use sp_sandbox::{EnvironmentDefinitionBuilder, Memory};35use sp_std::{prelude::*, convert::TryFrom, borrow::ToOwned};3637/// Pass to `create_code` in order to create a compiled `WasmModule`.38///39/// This exists to have a more declarative way to describe a wasm module than to use40/// parity-wasm directly. It is tailored to fit the structure of contracts that are41/// needed for benchmarking.42#[derive(Default)]43pub struct ModuleDefinition {44	/// Imported memory attached to the module. No memory is imported if `None`.45	pub memory: Option<ImportedMemory>,46	/// Initializers for the imported memory.47	pub data_segments: Vec<DataSegment>,48	/// Creates the supplied amount of i64 mutable globals initialized with random values.49	pub num_globals: u32,50	/// List of functions that the module should import. They start with index 0.51	pub imported_functions: Vec<ImportedFunction>,52	/// Function body of the exported `deploy` function. Body is empty if `None`.53	/// Its index is `imported_functions.len()`.54	pub deploy_body: Option<FuncBody>,55	/// Function body of the exported `call` function. Body is empty if `None`.56	/// Its index is `imported_functions.len() + 1`.57	pub call_body: Option<FuncBody>,58	/// Function body of a non-exported function with index `imported_functions.len() + 2`.59	pub aux_body: Option<FuncBody>,60	/// The amount of I64 arguments the aux function should have.61	pub aux_arg_num: u32,62	/// If set to true the stack height limiter is injected into the the module. This is63	/// needed for instruction debugging because the cost of executing the stack height64	/// instrumentation should be included in the costs for the individual instructions65	/// that cause more metering code (only call).66	pub inject_stack_metering: bool,67	/// Create a table containing function pointers.68	pub table: Option<TableSegment>,69	/// Create a section named "dummy" of the specified size. This is useful in order to70	/// benchmark the overhead of loading and storing codes of specified sizes. The dummy71	/// section only contributes to the size of the contract but does not affect execution.72	pub dummy_section: u32,73}7475pub struct TableSegment {76	/// How many elements should be created inside the table.77	pub num_elements: u32,78	/// The function index with which all table elements should be initialized.79	pub function_index: u32,80}8182pub struct DataSegment {83	pub offset: u32,84	pub value: Vec<u8>,85}8687#[derive(Clone)]88pub struct ImportedMemory {89	pub min_pages: u32,90	pub max_pages: u32,91}9293impl ImportedMemory {94	pub fn max<T: Config>() -> Self95	where96		T: Config,97		T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,98	{99		let pages = max_pages::<T>();100		Self {101			min_pages: pages,102			max_pages: pages,103		}104	}105}106107pub struct ImportedFunction {108	pub name: &'static str,109	pub params: Vec<ValueType>,110	pub return_type: Option<ValueType>,111}112113/// A wasm module ready to be put on chain.114#[derive(Clone)]115pub struct WasmModule<T: Config> {116	pub code: Vec<u8>,117	pub hash: <T::Hashing as Hash>::Output,118	memory: Option<ImportedMemory>,119}120121impl<T: Config> From<ModuleDefinition> for WasmModule<T>122where123	T: Config,124	T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,125{126	fn from(def: ModuleDefinition) -> Self {127		// internal functions start at that offset.128		let func_offset = u32::try_from(def.imported_functions.len()).unwrap();129130		// Every contract must export "deploy" and "call" functions131		let mut contract = parity_wasm::builder::module()132			// deploy function (first internal function)133			.function()134			.signature()135			.build()136			.with_body(137				def.deploy_body138					.unwrap_or_else(|| FuncBody::new(Vec::new(), Instructions::empty())),139			)140			.build()141			// call function (second internal function)142			.function()143			.signature()144			.build()145			.with_body(146				def.call_body147					.unwrap_or_else(|| FuncBody::new(Vec::new(), Instructions::empty())),148			)149			.build()150			.export()151			.field("deploy")152			.internal()153			.func(func_offset)154			.build()155			.export()156			.field("call")157			.internal()158			.func(func_offset + 1)159			.build();160161		// If specified we add an additional internal function162		if let Some(body) = def.aux_body {163			let mut signature = contract.function().signature();164			for _ in 0..def.aux_arg_num {165				signature = signature.with_param(ValueType::I64);166			}167			contract = signature.build().with_body(body).build();168		}169170		// Grant access to linear memory.171		if let Some(memory) = &def.memory {172			contract = contract173				.import()174				.module("env")175				.field("memory")176				.external()177				.memory(memory.min_pages, Some(memory.max_pages))178				.build();179		}180181		// Import supervisor functions. They start with idx 0.182		for func in def.imported_functions {183			let sig = parity_wasm::builder::signature()184				.with_params(func.params)185				.with_results(func.return_type.into_iter().collect())186				.build_sig();187			let sig = contract.push_signature(sig);188			contract = contract189				.import()190				.module("seal0")191				.field(func.name)192				.with_external(parity_wasm::elements::External::Function(sig))193				.build();194		}195196		// Initialize memory197		for data in def.data_segments {198			contract = contract199				.data()200				.offset(Instruction::I32Const(data.offset as i32))201				.value(data.value)202				.build()203		}204205		// Add global variables206		if def.num_globals > 0 {207			use rand::{prelude::*, distributions::Standard};208			let rng = rand_pcg::Pcg32::seed_from_u64(3112244599778833558);209			for val in rng.sample_iter(Standard).take(def.num_globals as usize) {210				contract = contract211					.global()212					.value_type()213					.i64()214					.mutable()215					.init_expr(Instruction::I64Const(val))216					.build()217			}218		}219220		// Add function pointer table221		if let Some(table) = def.table {222			contract = contract223				.table()224				.with_min(table.num_elements)225				.with_max(Some(table.num_elements))226				.with_element(0, vec![table.function_index; table.num_elements as usize])227				.build();228		}229230		// Add the dummy section231		if def.dummy_section > 0 {232			contract = contract.with_section(Section::Custom(CustomSection::new(233				"dummy".to_owned(),234				vec![42; def.dummy_section as usize],235			)));236		}237238		let mut code = contract.build();239240		// Inject stack height metering241		if def.inject_stack_metering {242			code = inject_limiter(code, <CurrentSchedule<T>>::get().limits.stack_height).unwrap();243		}244245		let code = code.to_bytes().unwrap();246		let hash = T::Hashing::hash(&code);247		Self {248			code,249			hash,250			memory: def.memory,251		}252	}253}254255impl<T: Config> WasmModule<T>256where257	T: Config,258	T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,259{260	/// Creates a wasm module with an empty `call` and `deploy` function and nothing else.261	pub fn dummy() -> Self {262		ModuleDefinition::default().into()263	}264265	/// Same as `dummy` but with maximum sized linear memory and a dummy section of specified size.266	pub fn dummy_with_bytes(dummy_bytes: u32) -> Self {267		ModuleDefinition {268			memory: Some(ImportedMemory::max::<T>()),269			dummy_section: dummy_bytes,270			..Default::default()271		}272		.into()273	}274275	/// Creates a wasm module of `target_bytes` size. Used to benchmark the performance of276	/// `instantiate_with_code` for different sizes of wasm modules. The generated module maximizes277	/// instrumentation runtime by nesting blocks as deeply as possible given the byte budget.278	pub fn sized(target_bytes: u32) -> Self {279		use parity_wasm::elements::Instruction::{If, I32Const, Return, End};280		// Base size of a contract is 63 bytes and each expansion adds 6 bytes.281		// We do one expansion less to account for the code section and function body282		// size fields inside the binary wasm module representation which are leb128 encoded283		// and therefore grow in size when the contract grows. We are not allowed to overshoot284		// because of the maximum code size that is enforced by `instantiate_with_code`.285		let expansions = (target_bytes.saturating_sub(63) / 6).saturating_sub(1);286		const EXPANSION: [Instruction; 4] = [I32Const(0), If(BlockType::NoResult), Return, End];287		ModuleDefinition {288			call_body: Some(body::repeated(expansions, &EXPANSION)),289			memory: Some(ImportedMemory::max::<T>()),290			..Default::default()291		}292		.into()293	}294295	/// Creates a wasm module that calls the imported function named `getter_name` `repeat`296	/// times. The imported function is expected to have the "getter signature" of297	/// (out_ptr: u32, len_ptr: u32) -> ().298	pub fn getter(getter_name: &'static str, repeat: u32) -> Self {299		let pages = max_pages::<T>();300		ModuleDefinition {301			memory: Some(ImportedMemory::max::<T>()),302			imported_functions: vec![ImportedFunction {303				name: getter_name,304				params: vec![ValueType::I32, ValueType::I32],305				return_type: None,306			}],307			// Write the output buffer size. The output size will be overwritten by the308			// supervisor with the real size when calling the getter. Since this size does not309			// change between calls it suffices to start with an initial value and then just310			// leave as whatever value was written there.311			data_segments: vec![DataSegment {312				offset: 0,313				value: (pages * 64 * 1024 - 4).to_le_bytes().to_vec(),314			}],315			call_body: Some(body::repeated(316				repeat,317				&[318					Instruction::I32Const(4), // ptr where to store output319					Instruction::I32Const(0), // ptr to length320					Instruction::Call(0),     // call the imported function321				],322			)),323			..Default::default()324		}325		.into()326	}327328	/// Creates a wasm module that calls the imported hash function named `name` `repeat` times329	/// with an input of size `data_size`. Hash functions have the signature330	/// (input_ptr: u32, input_len: u32, output_ptr: u32) -> ()331	pub fn hasher(name: &'static str, repeat: u32, data_size: u32) -> Self {332		ModuleDefinition {333			memory: Some(ImportedMemory::max::<T>()),334			imported_functions: vec![ImportedFunction {335				name,336				params: vec![ValueType::I32, ValueType::I32, ValueType::I32],337				return_type: None,338			}],339			call_body: Some(body::repeated(340				repeat,341				&[342					Instruction::I32Const(0),                // input_ptr343					Instruction::I32Const(data_size as i32), // input_len344					Instruction::I32Const(0),                // output_ptr345					Instruction::Call(0),346				],347			)),348			..Default::default()349		}350		.into()351	}352353	/// Creates a memory instance for use in a sandbox with dimensions declared in this module354	/// and adds it to `env`. A reference to that memory is returned so that it can be used to355	/// access the memory contents from the supervisor.356	pub fn add_memory<S>(&self, env: &mut EnvironmentDefinitionBuilder<S>) -> Option<Memory> {357		let memory = if let Some(memory) = &self.memory {358			memory359		} else {360			return None;361		};362		let memory = Memory::new(memory.min_pages, Some(memory.max_pages)).unwrap();363		env.add_memory("env", "memory", memory.clone());364		Some(memory)365	}366367	pub fn unary_instr(instr: Instruction, repeat: u32) -> Self {368		use body::DynInstr::{RandomI64Repeated, Regular};369		ModuleDefinition {370			call_body: Some(body::repeated_dyn(371				repeat,372				vec![373					RandomI64Repeated(1),374					Regular(instr),375					Regular(Instruction::Drop),376				],377			)),378			..Default::default()379		}380		.into()381	}382383	pub fn binary_instr(instr: Instruction, repeat: u32) -> Self {384		use body::DynInstr::{RandomI64Repeated, Regular};385		ModuleDefinition {386			call_body: Some(body::repeated_dyn(387				repeat,388				vec![389					RandomI64Repeated(2),390					Regular(instr),391					Regular(Instruction::Drop),392				],393			)),394			..Default::default()395		}396		.into()397	}398}399400/// Mechanisms to generate a function body that can be used inside a `ModuleDefinition`.401pub mod body {402	use super::*;403404	/// When generating contract code by repeating a wasm sequence, it's sometimes necessary405	/// to change those instructions on each repetition. The variants of this enum describe406	/// various ways in which this can happen.407	pub enum DynInstr {408		/// Insert the associated instruction.409		Regular(Instruction),410		/// Insert a I32Const with incrementing value for each insertion.411		/// (start_at, increment_by)412		Counter(u32, u32),413		/// Insert a I32Const with a random value in [low, high) not divisible by two.414		/// (low, high)415		RandomUnaligned(u32, u32),416		/// Insert a I32Const with a random value in [low, high).417		/// (low, high)418		RandomI32(i32, i32),419		/// Insert the specified amount of I32Const with a random value.420		RandomI32Repeated(usize),421		/// Insert the specified amount of I64Const with a random value.422		RandomI64Repeated(usize),423		/// Insert a GetLocal with a random offset in [low, high).424		/// (low, high)425		RandomGetLocal(u32, u32),426		/// Insert a SetLocal with a random offset in [low, high).427		/// (low, high)428		RandomSetLocal(u32, u32),429		/// Insert a TeeLocal with a random offset in [low, high).430		/// (low, high)431		RandomTeeLocal(u32, u32),432		/// Insert a GetGlobal with a random offset in [low, high).433		/// (low, high)434		RandomGetGlobal(u32, u32),435		/// Insert a SetGlobal with a random offset in [low, high).436		/// (low, high)437		RandomSetGlobal(u32, u32),438	}439440	pub fn plain(instructions: Vec<Instruction>) -> FuncBody {441		FuncBody::new(Vec::new(), Instructions::new(instructions))442	}443444	pub fn repeated(repetitions: u32, instructions: &[Instruction]) -> FuncBody {445		let instructions = Instructions::new(446			instructions447				.iter()448				.cycle()449				.take(instructions.len() * usize::try_from(repetitions).unwrap())450				.cloned()451				.chain(sp_std::iter::once(Instruction::End))452				.collect(),453		);454		FuncBody::new(Vec::new(), instructions)455	}456457	pub fn repeated_dyn(repetitions: u32, mut instructions: Vec<DynInstr>) -> FuncBody {458		use rand::{prelude::*, distributions::Standard};459460		// We do not need to be secure here.461		let mut rng = rand_pcg::Pcg32::seed_from_u64(8446744073709551615);462463		// We need to iterate over indices because we cannot cycle over mutable references464		let body = (0..instructions.len())465			.cycle()466			.take(instructions.len() * usize::try_from(repetitions).unwrap())467			.flat_map(|idx| match &mut instructions[idx] {468				DynInstr::Regular(instruction) => vec![instruction.clone()],469				DynInstr::Counter(offset, increment_by) => {470					let current = *offset;471					*offset += *increment_by;472					vec![Instruction::I32Const(current as i32)]473				}474				DynInstr::RandomUnaligned(low, high) => {475					let unaligned = rng.gen_range(*low..*high) | 1;476					vec![Instruction::I32Const(unaligned as i32)]477				}478				DynInstr::RandomI32(low, high) => {479					vec![Instruction::I32Const(rng.gen_range(*low..*high))]480				}481				DynInstr::RandomI32Repeated(num) => (&mut rng)482					.sample_iter(Standard)483					.take(*num)484					.map(|val| Instruction::I32Const(val))485					.collect(),486				DynInstr::RandomI64Repeated(num) => (&mut rng)487					.sample_iter(Standard)488					.take(*num)489					.map(|val| Instruction::I64Const(val))490					.collect(),491				DynInstr::RandomGetLocal(low, high) => {492					vec![Instruction::GetLocal(rng.gen_range(*low..*high))]493				}494				DynInstr::RandomSetLocal(low, high) => {495					vec![Instruction::SetLocal(rng.gen_range(*low..*high))]496				}497				DynInstr::RandomTeeLocal(low, high) => {498					vec![Instruction::TeeLocal(rng.gen_range(*low..*high))]499				}500				DynInstr::RandomGetGlobal(low, high) => {501					vec![Instruction::GetGlobal(rng.gen_range(*low..*high))]502				}503				DynInstr::RandomSetGlobal(low, high) => {504					vec![Instruction::SetGlobal(rng.gen_range(*low..*high))]505				}506			})507			.chain(sp_std::iter::once(Instruction::End))508			.collect();509		FuncBody::new(Vec::new(), Instructions::new(body))510	}511512	/// Replace the locals of the supplied `body` with `num` i64 locals.513	pub fn inject_locals(body: &mut FuncBody, num: u32) {514		use parity_wasm::elements::Local;515		*body.locals_mut() = (0..num).map(|i| Local::new(i, ValueType::I64)).collect()516	}517}518519/// The maximum amount of pages any contract is allowed to have according to the current `Schedule`.520pub fn max_pages<T: Config>() -> u32521where522	T: Config,523	T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,524{525	<CurrentSchedule<T>>::get().limits.memory_pages526}