Skip to content

Commit

Permalink
Refactor WASM module instantiation (paritytech#10480)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
koute authored and grishasobol committed Mar 28, 2022
1 parent 32ee913 commit 98ec2a0
Show file tree
Hide file tree
Showing 9 changed files with 468 additions and 323 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions client/executor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ sc-tracing = { version = "4.0.0-dev", path = "../tracing" }
tracing-subscriber = "0.2.19"
paste = "1.0"
regex = "1"
criterion = "0.3"
env_logger = "0.9"

[[bench]]
name = "bench"
harness = false

[features]
default = ["std"]
Expand Down
136 changes: 136 additions & 0 deletions client/executor/benches/bench.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// This file is part of Substrate.

// Copyright (C) 2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use criterion::{criterion_group, criterion_main, Criterion};

use sc_executor_common::{runtime_blob::RuntimeBlob, wasm_runtime::WasmModule};
use sc_runtime_test::wasm_binary_unwrap as test_runtime;
use sp_wasm_interface::HostFunctions as _;
use std::sync::Arc;

enum Method {
Interpreted,
#[cfg(feature = "wasmtime")]
Compiled {
fast_instance_reuse: bool,
},
}

// This is just a bog-standard Kusama runtime with the extra `test_empty_return`
// function copy-pasted from the test runtime.
fn kusama_runtime() -> &'static [u8] {
include_bytes!("kusama_runtime.wasm")
}

fn initialize(runtime: &[u8], method: Method) -> Arc<dyn WasmModule> {
let blob = RuntimeBlob::uncompress_if_needed(runtime).unwrap();
let host_functions = sp_io::SubstrateHostFunctions::host_functions();
let heap_pages = 2048;
let allow_missing_func_imports = true;

match method {
Method::Interpreted => sc_executor_wasmi::create_runtime(
blob,
heap_pages,
host_functions,
allow_missing_func_imports,
)
.map(|runtime| -> Arc<dyn WasmModule> { Arc::new(runtime) }),
#[cfg(feature = "wasmtime")]
Method::Compiled { fast_instance_reuse } =>
sc_executor_wasmtime::create_runtime::<sp_io::SubstrateHostFunctions>(
blob,
sc_executor_wasmtime::Config {
heap_pages,
max_memory_size: None,
allow_missing_func_imports,
cache_path: None,
semantics: sc_executor_wasmtime::Semantics {
fast_instance_reuse,
deterministic_stack_limit: None,
canonicalize_nans: false,
parallel_compilation: true,
},
},
)
.map(|runtime| -> Arc<dyn WasmModule> { Arc::new(runtime) }),
}
.unwrap()
}

fn bench_call_instance(c: &mut Criterion) {
let _ = env_logger::try_init();

#[cfg(feature = "wasmtime")]
{
let runtime = initialize(test_runtime(), Method::Compiled { fast_instance_reuse: true });
c.bench_function("call_instance_test_runtime_with_fast_instance_reuse", |b| {
let mut instance = runtime.new_instance().unwrap();
b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap())
});
}

#[cfg(feature = "wasmtime")]
{
let runtime = initialize(test_runtime(), Method::Compiled { fast_instance_reuse: false });
c.bench_function("call_instance_test_runtime_without_fast_instance_reuse", |b| {
let mut instance = runtime.new_instance().unwrap();
b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap());
});
}

#[cfg(feature = "wasmtime")]
{
let runtime = initialize(kusama_runtime(), Method::Compiled { fast_instance_reuse: true });
c.bench_function("call_instance_kusama_runtime_with_fast_instance_reuse", |b| {
let mut instance = runtime.new_instance().unwrap();
b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap())
});
}

#[cfg(feature = "wasmtime")]
{
let runtime = initialize(kusama_runtime(), Method::Compiled { fast_instance_reuse: false });
c.bench_function("call_instance_kusama_runtime_without_fast_instance_reuse", |b| {
let mut instance = runtime.new_instance().unwrap();
b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap());
});
}

{
let runtime = initialize(test_runtime(), Method::Interpreted);
c.bench_function("call_instance_test_runtime_interpreted", |b| {
let mut instance = runtime.new_instance().unwrap();
b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap())
});
}

{
let runtime = initialize(kusama_runtime(), Method::Interpreted);
c.bench_function("call_instance_kusama_runtime_interpreted", |b| {
let mut instance = runtime.new_instance().unwrap();
b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap())
});
}
}

criterion_group! {
name = benches;
config = Criterion::default();
targets = bench_call_instance
}
criterion_main!(benches);
Binary file added client/executor/benches/kusama_runtime.wasm
Binary file not shown.
84 changes: 83 additions & 1 deletion client/executor/common/src/runtime_blob/runtime_blob.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
use crate::error::WasmError;
use wasm_instrument::{
export_mutable_globals,
parity_wasm::elements::{deserialize_buffer, serialize, DataSegment, Internal, Module},
parity_wasm::elements::{
deserialize_buffer, serialize, DataSegment, ExportEntry, External, Internal, MemorySection,
MemoryType, Module, Section,
},
};

/// A bunch of information collected from a WebAssembly module.
Expand Down Expand Up @@ -104,6 +107,85 @@ impl RuntimeBlob {
.unwrap_or_default()
}

/// Converts a WASM memory import into a memory section and exports it.
///
/// Does nothing if there's no memory import.
///
/// May return an error in case the WASM module is invalid.
pub fn convert_memory_import_into_export(&mut self) -> Result<(), WasmError> {
let import_section = match self.raw_module.import_section_mut() {
Some(import_section) => import_section,
None => return Ok(()),
};

let import_entries = import_section.entries_mut();
for index in 0..import_entries.len() {
let entry = &import_entries[index];
let memory_ty = match entry.external() {
External::Memory(memory_ty) => *memory_ty,
_ => continue,
};

let memory_name = entry.field().to_owned();
import_entries.remove(index);

self.raw_module
.insert_section(Section::Memory(MemorySection::with_entries(vec![memory_ty])))
.map_err(|error| {
WasmError::Other(format!(
"can't convert a memory import into an export: failed to insert a new memory section: {}",
error
))
})?;

if self.raw_module.export_section_mut().is_none() {
// A module without an export section is somewhat unrealistic, but let's do this
// just in case to cover all of our bases.
self.raw_module
.insert_section(Section::Export(Default::default()))
.expect("an export section can be always inserted if it doesn't exist; qed");
}
self.raw_module
.export_section_mut()
.expect("export section already existed or we just added it above, so it always exists; qed")
.entries_mut()
.push(ExportEntry::new(memory_name, Internal::Memory(0)));

break
}

Ok(())
}

/// Increases the number of memory pages requested by the WASM blob by
/// the given amount of `extra_heap_pages`.
///
/// Will return an error in case there is no memory section present,
/// or if the memory section is empty.
///
/// Only modifies the initial size of the memory; the maximum is unmodified
/// unless it's smaller than the initial size, in which case it will be increased
/// so that it's at least as big as the initial size.
pub fn add_extra_heap_pages_to_memory_section(
&mut self,
extra_heap_pages: u32,
) -> Result<(), WasmError> {
let memory_section = self
.raw_module
.memory_section_mut()
.ok_or_else(|| WasmError::Other("no memory section found".into()))?;

if memory_section.entries().is_empty() {
return Err(WasmError::Other("memory section is empty".into()))
}
for memory_ty in memory_section.entries_mut() {
let min = memory_ty.limits().initial().saturating_add(extra_heap_pages);
let max = memory_ty.limits().maximum().map(|max| std::cmp::max(min, max));
*memory_ty = MemoryType::new(min, max);
}
Ok(())
}

/// Returns an iterator of all globals which were exported by [`expose_mutable_globals`].
pub(super) fn exported_internal_global_names<'module>(
&'module self,
Expand Down
Loading

0 comments on commit 98ec2a0

Please sign in to comment.