1// This file is part of Substrate.
23// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
56// 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 at
9//
10// http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// 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 and
16// 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 execution
21//! (API calls into seal, individual instructions), we need to generate contracts that
22//! perform those events. Because those contracts can get very big we cannot simply define
23//! them as text (.wat) as this will be too slow and consume too much memory. Therefore
24//! we define this simple definition of a contract that can be passed to `create_code` that
25//! compiles it down into a `WasmModule` that can be used as a contract's code.
2627use crate::Config;
28use alloc::{borrow::ToOwned, vec, vec::Vec};
29use frame_support::traits::Get;
30use sp_runtime::{traits::Hash, Saturating};
31use wasm_instrument::parity_wasm::{
32 builder,
33 elements::{
34self, BlockType, CustomSection, FuncBody, Instruction, Instructions, Local, Section,
35 ValueType,
36 },
37};
3839/// The location where to put the generated code.
40pub enum Location {
41/// Generate all code into the `call` exported function.
42Call,
43/// Generate all code into the `deploy` exported function.
44Deploy,
45}
4647/// Pass to `create_code` in order to create a compiled `WasmModule`.
48///
49/// This exists to have a more declarative way to describe a wasm module than to use
50/// parity-wasm directly. It is tailored to fit the structure of contracts that are
51/// needed for benchmarking.
52#[derive(Default)]
53pub struct ModuleDefinition {
54/// Imported memory attached to the module. No memory is imported if `None`.
55pub memory: Option<ImportedMemory>,
56/// Initializers for the imported memory.
57pub data_segments: Vec<DataSegment>,
58/// Creates the supplied amount of i64 mutable globals initialized with random values.
59pub num_globals: u32,
60/// List of functions that the module should import. They start with index 0.
61pub imported_functions: Vec<ImportedFunction>,
62/// Function body of the exported `deploy` function. Body is empty if `None`.
63 /// Its index is `imported_functions.len()`.
64pub deploy_body: Option<FuncBody>,
65/// Function body of the exported `call` function. Body is empty if `None`.
66 /// Its index is `imported_functions.len() + 1`.
67pub call_body: Option<FuncBody>,
68/// Function body of a non-exported function with index `imported_functions.len() + 2`.
69pub aux_body: Option<FuncBody>,
70/// The amount of I64 arguments the aux function should have.
71pub aux_arg_num: u32,
72/// Create a table containing function pointers.
73pub table: Option<TableSegment>,
74/// Create a section named "dummy" of the specified size. This is useful in order to
75 /// benchmark the overhead of loading and storing codes of specified sizes. The dummy
76 /// section only contributes to the size of the contract but does not affect execution.
77pub dummy_section: u32,
78}
7980pub struct TableSegment {
81/// How many elements should be created inside the table.
82pub num_elements: u32,
83/// The function index with which all table elements should be initialized.
84pub function_index: u32,
85}
8687pub struct DataSegment {
88pub offset: u32,
89pub value: Vec<u8>,
90}
9192#[derive(Clone)]
93pub struct ImportedMemory {
94pub min_pages: u32,
95pub max_pages: u32,
96}
9798impl ImportedMemory {
99pub fn max<T: Config>() -> Self {
100let pages = max_pages::<T>();
101Self { min_pages: pages, max_pages: pages }
102 }
103}
104105pub struct ImportedFunction {
106pub module: &'static str,
107pub name: &'static str,
108pub params: Vec<ValueType>,
109pub return_type: Option<ValueType>,
110}
111112/// A wasm module ready to be put on chain.
113#[derive(Clone)]
114pub struct WasmModule<T: Config> {
115pub code: Vec<u8>,
116pub hash: <T::Hashing as Hash>::Output,
117}
118119impl<T: Config> From<ModuleDefinition> for WasmModule<T> {
120fn from(def: ModuleDefinition) -> Self {
121// internal functions start at that offset.
122let func_offset = u32::try_from(def.imported_functions.len()).unwrap();
123124// Every contract must export "deploy" and "call" functions.
125let mut contract = builder::module()
126// deploy function (first internal function)
127.function()
128 .signature()
129 .build()
130 .with_body(
131 def.deploy_body
132 .unwrap_or_else(|| FuncBody::new(Vec::new(), Instructions::empty())),
133 )
134 .build()
135// call function (second internal function)
136.function()
137 .signature()
138 .build()
139 .with_body(
140 def.call_body
141 .unwrap_or_else(|| FuncBody::new(Vec::new(), Instructions::empty())),
142 )
143 .build()
144 .export()
145 .field("deploy")
146 .internal()
147 .func(func_offset)
148 .build()
149 .export()
150 .field("call")
151 .internal()
152 .func(func_offset + 1)
153 .build();
154155// If specified we add an additional internal function
156if let Some(body) = def.aux_body {
157let mut signature = contract.function().signature();
158for _ in 0..def.aux_arg_num {
159 signature = signature.with_param(ValueType::I64);
160 }
161 contract = signature.build().with_body(body).build();
162 }
163164// Grant access to linear memory.
165 // Every contract module is required to have an imported memory.
166 // If no memory is specified in the passed ModuleDefinition, then
167 // default to (1, 1).
168let (init, max) = if let Some(memory) = &def.memory {
169 (memory.min_pages, Some(memory.max_pages))
170 } else {
171 (1, Some(1))
172 };
173174 contract = contract.import().path("env", "memory").external().memory(init, max).build();
175176// Import supervisor functions. They start with idx 0.
177for func in def.imported_functions {
178let sig = builder::signature()
179 .with_params(func.params)
180 .with_results(func.return_type)
181 .build_sig();
182let sig = contract.push_signature(sig);
183 contract = contract
184 .import()
185 .module(func.module)
186 .field(func.name)
187 .with_external(elements::External::Function(sig))
188 .build();
189 }
190191// Initialize memory
192for data in def.data_segments {
193 contract = contract
194 .data()
195 .offset(Instruction::I32Const(data.offset as i32))
196 .value(data.value)
197 .build()
198 }
199200// Add global variables
201if def.num_globals > 0 {
202use rand::{distributions::Standard, prelude::*};
203let rng = rand_pcg::Pcg32::seed_from_u64(3112244599778833558);
204for val in rng.sample_iter(Standard).take(def.num_globals as usize) {
205 contract = contract
206 .global()
207 .value_type()
208 .i64()
209 .mutable()
210 .init_expr(Instruction::I64Const(val))
211 .build()
212 }
213 }
214215// Add function pointer table
216if let Some(table) = def.table {
217 contract = contract
218 .table()
219 .with_min(table.num_elements)
220 .with_max(Some(table.num_elements))
221 .with_element(0, vec![table.function_index; table.num_elements as usize])
222 .build();
223 }
224225// Add the dummy section
226if def.dummy_section > 0 {
227 contract = contract.with_section(Section::Custom(CustomSection::new(
228"dummy".to_owned(),
229vec![42; def.dummy_section as usize],
230 )));
231 }
232233let code = contract.build().into_bytes().unwrap();
234let hash = T::Hashing::hash(&code);
235Self { code: code.into(), hash }
236 }
237}
238239impl<T: Config> WasmModule<T> {
240/// Creates a wasm module with an empty `call` and `deploy` function and nothing else.
241pub fn dummy() -> Self {
242 ModuleDefinition::default().into()
243 }
244245/// Same as `dummy` but with maximum sized linear memory and a dummy section of specified size.
246pub fn dummy_with_bytes(dummy_bytes: u32) -> Self {
247// We want the module to have the size `dummy_bytes`.
248 // This is not completely correct as the overhead grows when the contract grows
249 // because of variable length integer encoding. However, it is good enough to be that
250 // close for benchmarking purposes.
251let module_overhead = 65;
252 ModuleDefinition {
253 memory: Some(ImportedMemory::max::<T>()),
254 dummy_section: dummy_bytes.saturating_sub(module_overhead),
255 ..Default::default()
256 }
257 .into()
258 }
259260/// Creates a wasm module of `target_bytes` size. Used to benchmark the performance of
261 /// `instantiate_with_code` for different sizes of wasm modules. The generated module maximizes
262 /// instrumentation runtime by nesting blocks as deeply as possible given the byte budget.
263 /// `code_location`: Whether to place the code into `deploy` or `call`.
264pub fn sized(target_bytes: u32, code_location: Location, use_float: bool) -> Self {
265use self::elements::Instruction::{End, GetLocal, If, Return};
266// Base size of a contract is 63 bytes and each expansion adds 6 bytes.
267 // We do one expansion less to account for the code section and function body
268 // size fields inside the binary wasm module representation which are leb128 encoded
269 // and therefore grow in size when the contract grows. We are not allowed to overshoot
270 // because of the maximum code size that is enforced by `instantiate_with_code`.
271let mut expansions = (target_bytes.saturating_sub(63) / 6).saturating_sub(1);
272const EXPANSION: [Instruction; 4] = [GetLocal(0), If(BlockType::NoResult), Return, End];
273let mut locals = vec![Local::new(1, ValueType::I32)];
274if use_float {
275 locals.push(Local::new(1, ValueType::F32));
276 locals.push(Local::new(2, ValueType::F32));
277 locals.push(Local::new(3, ValueType::F32));
278 expansions.saturating_dec();
279 }
280let mut module =
281 ModuleDefinition { memory: Some(ImportedMemory::max::<T>()), ..Default::default() };
282let body = Some(body::repeated_with_locals(&locals, expansions, &EXPANSION));
283match code_location {
284 Location::Call => module.call_body = body,
285 Location::Deploy => module.deploy_body = body,
286 }
287 module.into()
288 }
289290/// Creates a wasm module that calls the imported function `noop` `repeat` times.
291pub fn noop(repeat: u32) -> Self {
292let pages = max_pages::<T>();
293 ModuleDefinition {
294 memory: Some(ImportedMemory::max::<T>()),
295 imported_functions: vec![ImportedFunction {
296 module: "seal0",
297 name: "noop",
298 params: vec![],
299 return_type: None,
300 }],
301// Write the output buffer size. The output size will be overwritten by the
302 // supervisor with the real size when calling the getter. Since this size does not
303 // change between calls it suffices to start with an initial value and then just
304 // leave as whatever value was written there.
305data_segments: vec![DataSegment {
306 offset: 0,
307 value: (pages * 64 * 1024 - 4).to_le_bytes().to_vec(),
308 }],
309 call_body: Some(body::repeated(
310 repeat,
311&[
312 Instruction::Call(0), // call the imported function
313],
314 )),
315 ..Default::default()
316 }
317 .into()
318 }
319}
320321/// Mechanisms to generate a function body that can be used inside a `ModuleDefinition`.
322pub mod body {
323use super::*;
324325pub fn repeated(repetitions: u32, instructions: &[Instruction]) -> FuncBody {
326 repeated_with_locals(&[], repetitions, instructions)
327 }
328329pub fn repeated_with_locals(
330 locals: &[Local],
331 repetitions: u32,
332 instructions: &[Instruction],
333 ) -> FuncBody {
334let instructions = Instructions::new(
335 instructions
336 .iter()
337 .cycle()
338 .take(instructions.len() * usize::try_from(repetitions).unwrap())
339 .cloned()
340 .chain(core::iter::once(Instruction::End))
341 .collect(),
342 );
343 FuncBody::new(locals.to_vec(), instructions)
344 }
345346pub fn repeated_with_locals_using<const N: usize>(
347 locals: &[Local],
348 repetitions: u32,
349mut f: impl FnMut() -> [Instruction; N],
350 ) -> FuncBody {
351let mut instructions = Vec::new();
352for _ in 0..repetitions {
353 instructions.extend(f());
354 }
355 instructions.push(Instruction::End);
356 FuncBody::new(locals.to_vec(), Instructions::new(instructions))
357 }
358}
359360/// The maximum amount of pages any contract is allowed to have according to the current `Schedule`.
361pub fn max_pages<T: Config>() -> u32 {
362 T::Schedule::get().limits.memory_pages
363}