pallet_contracts/benchmarking/
code.rs1use 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::{
34 self, BlockType, CustomSection, FuncBody, Instruction, Instructions, Local, Section,
35 ValueType,
36 },
37};
38
39pub enum Location {
41 Call,
43 Deploy,
45}
46
47#[derive(Default)]
53pub struct ModuleDefinition {
54 pub memory: Option<ImportedMemory>,
56 pub data_segments: Vec<DataSegment>,
58 pub num_globals: u32,
60 pub imported_functions: Vec<ImportedFunction>,
62 pub deploy_body: Option<FuncBody>,
65 pub call_body: Option<FuncBody>,
68 pub aux_body: Option<FuncBody>,
70 pub aux_arg_num: u32,
72 pub table: Option<TableSegment>,
74 pub dummy_section: u32,
78}
79
80pub struct TableSegment {
81 pub num_elements: u32,
83 pub function_index: u32,
85}
86
87pub struct DataSegment {
88 pub offset: u32,
89 pub value: Vec<u8>,
90}
91
92#[derive(Clone)]
93pub struct ImportedMemory {
94 pub min_pages: u32,
95 pub max_pages: u32,
96}
97
98impl ImportedMemory {
99 pub fn max<T: Config>() -> Self {
100 let pages = max_pages::<T>();
101 Self { min_pages: pages, max_pages: pages }
102 }
103}
104
105pub struct ImportedFunction {
106 pub module: &'static str,
107 pub name: &'static str,
108 pub params: Vec<ValueType>,
109 pub return_type: Option<ValueType>,
110}
111
112#[derive(Clone)]
114pub struct WasmModule<T: Config> {
115 pub code: Vec<u8>,
116 pub hash: <T::Hashing as Hash>::Output,
117}
118
119impl<T: Config> From<ModuleDefinition> for WasmModule<T> {
120 fn from(def: ModuleDefinition) -> Self {
121 let func_offset = u32::try_from(def.imported_functions.len()).unwrap();
123
124 let mut contract = builder::module()
126 .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 .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();
154
155 if let Some(body) = def.aux_body {
157 let mut signature = contract.function().signature();
158 for _ in 0..def.aux_arg_num {
159 signature = signature.with_param(ValueType::I64);
160 }
161 contract = signature.build().with_body(body).build();
162 }
163
164 let (init, max) = if let Some(memory) = &def.memory {
169 (memory.min_pages, Some(memory.max_pages))
170 } else {
171 (1, Some(1))
172 };
173
174 contract = contract.import().path("env", "memory").external().memory(init, max).build();
175
176 for func in def.imported_functions {
178 let sig = builder::signature()
179 .with_params(func.params)
180 .with_results(func.return_type)
181 .build_sig();
182 let 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 }
190
191 for 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 }
199
200 if def.num_globals > 0 {
202 use rand::{distributions::Standard, prelude::*};
203 let rng = rand_pcg::Pcg32::seed_from_u64(3112244599778833558);
204 for 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 }
214
215 if 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 }
224
225 if def.dummy_section > 0 {
227 contract = contract.with_section(Section::Custom(CustomSection::new(
228 "dummy".to_owned(),
229 vec![42; def.dummy_section as usize],
230 )));
231 }
232
233 let code = contract.build().into_bytes().unwrap();
234 let hash = T::Hashing::hash(&code);
235 Self { code: code.into(), hash }
236 }
237}
238
239impl<T: Config> WasmModule<T> {
240 pub fn dummy() -> Self {
242 ModuleDefinition::default().into()
243 }
244
245 pub fn dummy_with_bytes(dummy_bytes: u32) -> Self {
247 let 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 }
259
260 pub fn sized(target_bytes: u32, code_location: Location, use_float: bool) -> Self {
265 use self::elements::Instruction::{End, GetLocal, If, Return};
266 let mut expansions = (target_bytes.saturating_sub(63) / 6).saturating_sub(1);
272 const EXPANSION: [Instruction; 4] = [GetLocal(0), If(BlockType::NoResult), Return, End];
273 let mut locals = vec![Local::new(1, ValueType::I32)];
274 if 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 }
280 let mut module =
281 ModuleDefinition { memory: Some(ImportedMemory::max::<T>()), ..Default::default() };
282 let body = Some(body::repeated_with_locals(&locals, expansions, &EXPANSION));
283 match code_location {
284 Location::Call => module.call_body = body,
285 Location::Deploy => module.deploy_body = body,
286 }
287 module.into()
288 }
289
290 pub fn noop(repeat: u32) -> Self {
292 let 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 data_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), ],
314 )),
315 ..Default::default()
316 }
317 .into()
318 }
319}
320
321pub mod body {
323 use super::*;
324
325 pub fn repeated(repetitions: u32, instructions: &[Instruction]) -> FuncBody {
326 repeated_with_locals(&[], repetitions, instructions)
327 }
328
329 pub fn repeated_with_locals(
330 locals: &[Local],
331 repetitions: u32,
332 instructions: &[Instruction],
333 ) -> FuncBody {
334 let 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 }
345
346 pub fn repeated_with_locals_using<const N: usize>(
347 locals: &[Local],
348 repetitions: u32,
349 mut f: impl FnMut() -> [Instruction; N],
350 ) -> FuncBody {
351 let mut instructions = Vec::new();
352 for _ in 0..repetitions {
353 instructions.extend(f());
354 }
355 instructions.push(Instruction::End);
356 FuncBody::new(locals.to_vec(), Instructions::new(instructions))
357 }
358}
359
360pub fn max_pages<T: Config>() -> u32 {
362 T::Schedule::get().limits.memory_pages
363}