Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit b64647c

Browse files
authored
Refactor WASM module instantiation (#10480)
* Refactor WASM module instantiation; enable WASM instance pooling * Disable the `uffd` feature on `wasmtime` * Restore the original behavior regarding the initial WASM memory size * Adjust error message * Remove unnecessary import in the benchmarks * Preinstantiate the WASM runtime for a slight speedup * Delete the asserts in `convert_memory_import_into_export` * `return` -> `break` * Revert WASM instance pooling for now * Have `convert_memory_import_into_export` return an error instead of panic * Update the warning when an import is missing * Rustfmt and clippy fix * Fix executor benchmarks' compilation without `wasmtime` being enabled * rustfmt again * Align to review comments * Extend tests so that both imported and exported memories are tested * Increase the number of heap pages for exported memories too * Fix `decommit_works` test
1 parent 8cbda80 commit b64647c

File tree

9 files changed

+468
-323
lines changed

9 files changed

+468
-323
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/executor/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ sc-tracing = { version = "4.0.0-dev", path = "../tracing" }
4747
tracing-subscriber = "0.2.19"
4848
paste = "1.0"
4949
regex = "1"
50+
criterion = "0.3"
51+
env_logger = "0.9"
52+
53+
[[bench]]
54+
name = "bench"
55+
harness = false
5056

5157
[features]
5258
default = ["std"]

client/executor/benches/bench.rs

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// This file is part of Substrate.
2+
3+
// Copyright (C) 2021 Parity Technologies (UK) Ltd.
4+
// SPDX-License-Identifier: Apache-2.0
5+
6+
// 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.
17+
18+
use criterion::{criterion_group, criterion_main, Criterion};
19+
20+
use sc_executor_common::{runtime_blob::RuntimeBlob, wasm_runtime::WasmModule};
21+
use sc_runtime_test::wasm_binary_unwrap as test_runtime;
22+
use sp_wasm_interface::HostFunctions as _;
23+
use std::sync::Arc;
24+
25+
enum Method {
26+
Interpreted,
27+
#[cfg(feature = "wasmtime")]
28+
Compiled {
29+
fast_instance_reuse: bool,
30+
},
31+
}
32+
33+
// This is just a bog-standard Kusama runtime with the extra `test_empty_return`
34+
// function copy-pasted from the test runtime.
35+
fn kusama_runtime() -> &'static [u8] {
36+
include_bytes!("kusama_runtime.wasm")
37+
}
38+
39+
fn initialize(runtime: &[u8], method: Method) -> Arc<dyn WasmModule> {
40+
let blob = RuntimeBlob::uncompress_if_needed(runtime).unwrap();
41+
let host_functions = sp_io::SubstrateHostFunctions::host_functions();
42+
let heap_pages = 2048;
43+
let allow_missing_func_imports = true;
44+
45+
match method {
46+
Method::Interpreted => sc_executor_wasmi::create_runtime(
47+
blob,
48+
heap_pages,
49+
host_functions,
50+
allow_missing_func_imports,
51+
)
52+
.map(|runtime| -> Arc<dyn WasmModule> { Arc::new(runtime) }),
53+
#[cfg(feature = "wasmtime")]
54+
Method::Compiled { fast_instance_reuse } =>
55+
sc_executor_wasmtime::create_runtime::<sp_io::SubstrateHostFunctions>(
56+
blob,
57+
sc_executor_wasmtime::Config {
58+
heap_pages,
59+
max_memory_size: None,
60+
allow_missing_func_imports,
61+
cache_path: None,
62+
semantics: sc_executor_wasmtime::Semantics {
63+
fast_instance_reuse,
64+
deterministic_stack_limit: None,
65+
canonicalize_nans: false,
66+
parallel_compilation: true,
67+
},
68+
},
69+
)
70+
.map(|runtime| -> Arc<dyn WasmModule> { Arc::new(runtime) }),
71+
}
72+
.unwrap()
73+
}
74+
75+
fn bench_call_instance(c: &mut Criterion) {
76+
let _ = env_logger::try_init();
77+
78+
#[cfg(feature = "wasmtime")]
79+
{
80+
let runtime = initialize(test_runtime(), Method::Compiled { fast_instance_reuse: true });
81+
c.bench_function("call_instance_test_runtime_with_fast_instance_reuse", |b| {
82+
let mut instance = runtime.new_instance().unwrap();
83+
b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap())
84+
});
85+
}
86+
87+
#[cfg(feature = "wasmtime")]
88+
{
89+
let runtime = initialize(test_runtime(), Method::Compiled { fast_instance_reuse: false });
90+
c.bench_function("call_instance_test_runtime_without_fast_instance_reuse", |b| {
91+
let mut instance = runtime.new_instance().unwrap();
92+
b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap());
93+
});
94+
}
95+
96+
#[cfg(feature = "wasmtime")]
97+
{
98+
let runtime = initialize(kusama_runtime(), Method::Compiled { fast_instance_reuse: true });
99+
c.bench_function("call_instance_kusama_runtime_with_fast_instance_reuse", |b| {
100+
let mut instance = runtime.new_instance().unwrap();
101+
b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap())
102+
});
103+
}
104+
105+
#[cfg(feature = "wasmtime")]
106+
{
107+
let runtime = initialize(kusama_runtime(), Method::Compiled { fast_instance_reuse: false });
108+
c.bench_function("call_instance_kusama_runtime_without_fast_instance_reuse", |b| {
109+
let mut instance = runtime.new_instance().unwrap();
110+
b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap());
111+
});
112+
}
113+
114+
{
115+
let runtime = initialize(test_runtime(), Method::Interpreted);
116+
c.bench_function("call_instance_test_runtime_interpreted", |b| {
117+
let mut instance = runtime.new_instance().unwrap();
118+
b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap())
119+
});
120+
}
121+
122+
{
123+
let runtime = initialize(kusama_runtime(), Method::Interpreted);
124+
c.bench_function("call_instance_kusama_runtime_interpreted", |b| {
125+
let mut instance = runtime.new_instance().unwrap();
126+
b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap())
127+
});
128+
}
129+
}
130+
131+
criterion_group! {
132+
name = benches;
133+
config = Criterion::default();
134+
targets = bench_call_instance
135+
}
136+
criterion_main!(benches);
5.3 MB
Binary file not shown.

client/executor/common/src/runtime_blob/runtime_blob.rs

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@
1919
use crate::error::WasmError;
2020
use wasm_instrument::{
2121
export_mutable_globals,
22-
parity_wasm::elements::{deserialize_buffer, serialize, DataSegment, Internal, Module},
22+
parity_wasm::elements::{
23+
deserialize_buffer, serialize, DataSegment, ExportEntry, External, Internal, MemorySection,
24+
MemoryType, Module, Section,
25+
},
2326
};
2427

2528
/// A bunch of information collected from a WebAssembly module.
@@ -104,6 +107,85 @@ impl RuntimeBlob {
104107
.unwrap_or_default()
105108
}
106109

110+
/// Converts a WASM memory import into a memory section and exports it.
111+
///
112+
/// Does nothing if there's no memory import.
113+
///
114+
/// May return an error in case the WASM module is invalid.
115+
pub fn convert_memory_import_into_export(&mut self) -> Result<(), WasmError> {
116+
let import_section = match self.raw_module.import_section_mut() {
117+
Some(import_section) => import_section,
118+
None => return Ok(()),
119+
};
120+
121+
let import_entries = import_section.entries_mut();
122+
for index in 0..import_entries.len() {
123+
let entry = &import_entries[index];
124+
let memory_ty = match entry.external() {
125+
External::Memory(memory_ty) => *memory_ty,
126+
_ => continue,
127+
};
128+
129+
let memory_name = entry.field().to_owned();
130+
import_entries.remove(index);
131+
132+
self.raw_module
133+
.insert_section(Section::Memory(MemorySection::with_entries(vec![memory_ty])))
134+
.map_err(|error| {
135+
WasmError::Other(format!(
136+
"can't convert a memory import into an export: failed to insert a new memory section: {}",
137+
error
138+
))
139+
})?;
140+
141+
if self.raw_module.export_section_mut().is_none() {
142+
// A module without an export section is somewhat unrealistic, but let's do this
143+
// just in case to cover all of our bases.
144+
self.raw_module
145+
.insert_section(Section::Export(Default::default()))
146+
.expect("an export section can be always inserted if it doesn't exist; qed");
147+
}
148+
self.raw_module
149+
.export_section_mut()
150+
.expect("export section already existed or we just added it above, so it always exists; qed")
151+
.entries_mut()
152+
.push(ExportEntry::new(memory_name, Internal::Memory(0)));
153+
154+
break
155+
}
156+
157+
Ok(())
158+
}
159+
160+
/// Increases the number of memory pages requested by the WASM blob by
161+
/// the given amount of `extra_heap_pages`.
162+
///
163+
/// Will return an error in case there is no memory section present,
164+
/// or if the memory section is empty.
165+
///
166+
/// Only modifies the initial size of the memory; the maximum is unmodified
167+
/// unless it's smaller than the initial size, in which case it will be increased
168+
/// so that it's at least as big as the initial size.
169+
pub fn add_extra_heap_pages_to_memory_section(
170+
&mut self,
171+
extra_heap_pages: u32,
172+
) -> Result<(), WasmError> {
173+
let memory_section = self
174+
.raw_module
175+
.memory_section_mut()
176+
.ok_or_else(|| WasmError::Other("no memory section found".into()))?;
177+
178+
if memory_section.entries().is_empty() {
179+
return Err(WasmError::Other("memory section is empty".into()))
180+
}
181+
for memory_ty in memory_section.entries_mut() {
182+
let min = memory_ty.limits().initial().saturating_add(extra_heap_pages);
183+
let max = memory_ty.limits().maximum().map(|max| std::cmp::max(min, max));
184+
*memory_ty = MemoryType::new(min, max);
185+
}
186+
Ok(())
187+
}
188+
107189
/// Returns an iterator of all globals which were exported by [`expose_mutable_globals`].
108190
pub(super) fn exported_internal_global_names<'module>(
109191
&'module self,

0 commit comments

Comments
 (0)