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

Add extra WASM heap pages when precompiling the runtime blob #11107

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/executor/benches/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ fn initialize(runtime: &[u8], method: Method) -> Arc<dyn WasmModule> {
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 {
extra_heap_pages: heap_pages,
fast_instance_reuse,
deterministic_stack_limit: None,
canonicalize_nans: false,
Expand Down
2 changes: 1 addition & 1 deletion client/executor/src/wasm_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,11 +317,11 @@ where
WasmExecutionMethod::Compiled => sc_executor_wasmtime::create_runtime::<H>(
blob,
sc_executor_wasmtime::Config {
heap_pages,
max_memory_size: None,
allow_missing_func_imports,
cache_path: cache_path.map(ToOwned::to_owned),
semantics: sc_executor_wasmtime::Semantics {
extra_heap_pages: heap_pages,
fast_instance_reuse: true,
deterministic_stack_limit: None,
canonicalize_nans: false,
Expand Down
47 changes: 24 additions & 23 deletions client/executor/wasmtime/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,20 +414,22 @@ pub struct Semantics {

/// Configures wasmtime to use multiple threads for compiling.
pub parallel_compilation: bool,

/// The number of extra WASM pages which will be allocated
/// on top of what is requested by the WASM blob itself.
pub extra_heap_pages: u64,
}

pub struct Config {
/// The number of wasm pages to be mounted after instantiation.
pub heap_pages: u64,

/// The total amount of memory in bytes an instance can request.
///
/// If specified, the runtime will be able to allocate only that much of wasm memory.
/// This is the total number and therefore the [`Config::heap_pages`] is accounted for.
/// This is the total number and therefore the [`Semantics::extra_heap_pages`] is accounted
/// for.
///
/// That means that the initial number of pages of a linear memory plus the
/// [`Config::heap_pages`] multiplied by the wasm page size (64KiB) should be less than or
/// equal to `max_memory_size`, otherwise the instance won't be created.
/// [`Semantics::extra_heap_pages`] multiplied by the wasm page size (64KiB) should be less
/// than or equal to `max_memory_size`, otherwise the instance won't be created.
///
/// Moreover, `memory.grow` will fail (return -1) if the sum of sizes of currently mounted
/// and additional pages exceeds `max_memory_size`.
Expand Down Expand Up @@ -534,21 +536,7 @@ where

let (module, snapshot_data) = match code_supply_mode {
CodeSupplyMode::Verbatim { blob } => {
let mut blob = instrument(blob, &config.semantics)?;

// We don't actually need the memory to be imported so we can just convert any memory
// import into an export with impunity. This simplifies our code since `wasmtime` will
// now automatically take care of creating the memory for us, and it also allows us
// to potentially enable `wasmtime`'s instance pooling at a later date. (Imported
// memories are ineligible for pooling.)
blob.convert_memory_import_into_export()?;
blob.add_extra_heap_pages_to_memory_section(
config
.heap_pages
.try_into()
.map_err(|e| WasmError::Other(format!("invalid `heap_pages`: {}", e)))?,
)?;

let blob = prepare_blob_for_compilation(blob, &config.semantics)?;
let serialized_blob = blob.clone().serialize();

let module = wasmtime::Module::new(&engine, &serialized_blob)
Expand Down Expand Up @@ -587,7 +575,7 @@ where
Ok(WasmtimeRuntime { engine, instance_pre: Arc::new(instance_pre), snapshot_data, config })
}

fn instrument(
fn prepare_blob_for_compilation(
mut blob: RuntimeBlob,
semantics: &Semantics,
) -> std::result::Result<RuntimeBlob, WasmError> {
Expand All @@ -600,6 +588,19 @@ fn instrument(
blob.expose_mutable_globals();
}

// We don't actually need the memory to be imported so we can just convert any memory
// import into an export with impunity. This simplifies our code since `wasmtime` will
// now automatically take care of creating the memory for us, and it also allows us
// to potentially enable `wasmtime`'s instance pooling at a later date. (Imported
// memories are ineligible for pooling.)
blob.convert_memory_import_into_export()?;
blob.add_extra_heap_pages_to_memory_section(
semantics
.extra_heap_pages
.try_into()
.map_err(|e| WasmError::Other(format!("invalid `extra_heap_pages`: {}", e)))?,
)?;

Ok(blob)
}

Expand All @@ -609,7 +610,7 @@ pub fn prepare_runtime_artifact(
blob: RuntimeBlob,
semantics: &Semantics,
) -> std::result::Result<Vec<u8>, WasmError> {
let blob = instrument(blob, semantics)?;
let blob = prepare_blob_for_compilation(blob, semantics)?;

let engine = Engine::new(&common_config(semantics)?)
.map_err(|e| WasmError::Other(format!("cannot create the engine: {}", e)))?;
Expand Down
124 changes: 74 additions & 50 deletions client/executor/wasmtime/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ struct RuntimeBuilder {
fast_instance_reuse: bool,
canonicalize_nans: bool,
deterministic_stack: bool,
heap_pages: u64,
extra_heap_pages: u64,
max_memory_size: Option<usize>,
precompile_runtime: bool,
}

impl RuntimeBuilder {
Expand All @@ -41,34 +42,44 @@ impl RuntimeBuilder {
fast_instance_reuse: false,
canonicalize_nans: false,
deterministic_stack: false,
heap_pages: 1024,
extra_heap_pages: 1024,
max_memory_size: None,
precompile_runtime: false,
}
}

fn use_wat(&mut self, code: String) {
fn use_wat(&mut self, code: String) -> &mut Self {
self.code = Some(code);
self
}

fn canonicalize_nans(&mut self, canonicalize_nans: bool) {
fn canonicalize_nans(&mut self, canonicalize_nans: bool) -> &mut Self {
self.canonicalize_nans = canonicalize_nans;
self
}

fn deterministic_stack(&mut self, deterministic_stack: bool) {
fn deterministic_stack(&mut self, deterministic_stack: bool) -> &mut Self {
self.deterministic_stack = deterministic_stack;
self
}

fn max_memory_size(&mut self, max_memory_size: Option<usize>) {
fn precompile_runtime(&mut self, precompile_runtime: bool) -> &mut Self {
self.precompile_runtime = precompile_runtime;
self
}

fn max_memory_size(&mut self, max_memory_size: Option<usize>) -> &mut Self {
self.max_memory_size = max_memory_size;
self
}

fn build(self) -> Arc<dyn WasmModule> {
fn build(&mut self) -> Arc<dyn WasmModule> {
let blob = {
let wasm: Vec<u8>;

let wasm = match self.code {
None => wasm_binary_unwrap(),
Some(wat) => {
Some(ref wat) => {
wasm = wat::parse_str(wat).expect("wat parsing failed");
&wasm
},
Expand All @@ -78,27 +89,31 @@ impl RuntimeBuilder {
.expect("failed to create a runtime blob out of test runtime")
};

let rt = crate::create_runtime::<HostFunctions>(
blob,
crate::Config {
heap_pages: self.heap_pages,
max_memory_size: self.max_memory_size,
allow_missing_func_imports: true,
cache_path: None,
semantics: crate::Semantics {
fast_instance_reuse: self.fast_instance_reuse,
deterministic_stack_limit: match self.deterministic_stack {
true => Some(crate::DeterministicStackLimit {
logical_max: 65536,
native_stack_max: 256 * 1024 * 1024,
}),
false => None,
},
canonicalize_nans: self.canonicalize_nans,
parallel_compilation: true,
let config = crate::Config {
max_memory_size: self.max_memory_size,
allow_missing_func_imports: true,
cache_path: None,
semantics: crate::Semantics {
fast_instance_reuse: self.fast_instance_reuse,
deterministic_stack_limit: match self.deterministic_stack {
true => Some(crate::DeterministicStackLimit {
logical_max: 65536,
native_stack_max: 256 * 1024 * 1024,
}),
false => None,
},
canonicalize_nans: self.canonicalize_nans,
parallel_compilation: true,
extra_heap_pages: self.extra_heap_pages,
},
)
};

let rt = if self.precompile_runtime {
let artifact = crate::prepare_runtime_artifact(blob, &config.semantics).unwrap();
unsafe { crate::create_runtime_from_artifact::<HostFunctions>(&artifact, config) }
} else {
crate::create_runtime::<HostFunctions>(blob, config)
}
.expect("cannot create runtime");

Arc::new(rt) as Arc<dyn WasmModule>
Expand All @@ -107,11 +122,7 @@ impl RuntimeBuilder {

#[test]
fn test_nan_canonicalization() {
let runtime = {
let mut builder = RuntimeBuilder::new_on_demand();
builder.canonicalize_nans(true);
builder.build()
};
let runtime = RuntimeBuilder::new_on_demand().canonicalize_nans(true).build();

let mut instance = runtime.new_instance().expect("failed to instantiate a runtime");

Expand Down Expand Up @@ -150,12 +161,10 @@ fn test_nan_canonicalization() {
fn test_stack_depth_reaching() {
const TEST_GUARD_PAGE_SKIP: &str = include_str!("test-guard-page-skip.wat");

let runtime = {
let mut builder = RuntimeBuilder::new_on_demand();
builder.use_wat(TEST_GUARD_PAGE_SKIP.to_string());
builder.deterministic_stack(true);
builder.build()
};
let runtime = RuntimeBuilder::new_on_demand()
.use_wat(TEST_GUARD_PAGE_SKIP.to_string())
.deterministic_stack(true)
.build();
let mut instance = runtime.new_instance().expect("failed to instantiate a runtime");

match instance.call_export("test-many-locals", &[]).unwrap_err() {
Expand All @@ -168,26 +177,36 @@ fn test_stack_depth_reaching() {
}

#[test]
fn test_max_memory_pages_imported_memory() {
test_max_memory_pages(true);
fn test_max_memory_pages_imported_memory_without_precompilation() {
test_max_memory_pages(true, false);
}

#[test]
fn test_max_memory_pages_exported_memory_without_precompilation() {
test_max_memory_pages(false, false);
}

#[test]
fn test_max_memory_pages_exported_memory() {
test_max_memory_pages(false);
fn test_max_memory_pages_imported_memory_with_precompilation() {
test_max_memory_pages(true, true);
}

fn test_max_memory_pages(import_memory: bool) {
#[test]
fn test_max_memory_pages_exported_memory_with_precompilation() {
test_max_memory_pages(false, true);
}

fn test_max_memory_pages(import_memory: bool, precompile_runtime: bool) {
fn try_instantiate(
max_memory_size: Option<usize>,
wat: String,
precompile_runtime: bool,
) -> Result<(), Box<dyn std::error::Error>> {
let runtime = {
let mut builder = RuntimeBuilder::new_on_demand();
builder.use_wat(wat);
builder.max_memory_size(max_memory_size);
builder.build()
};
let runtime = RuntimeBuilder::new_on_demand()
.use_wat(wat)
.max_memory_size(max_memory_size)
.precompile_runtime(precompile_runtime)
.build();
let mut instance = runtime.new_instance()?;
let _ = instance.call_export("main", &[])?;
Ok(())
Expand Down Expand Up @@ -235,6 +254,7 @@ fn test_max_memory_pages(import_memory: bool) {
*/
memory(64511, None, import_memory)
),
precompile_runtime,
)
.unwrap();

Expand All @@ -257,6 +277,7 @@ fn test_max_memory_pages(import_memory: bool) {
// 1 initial, max is not specified.
memory(1, None, import_memory)
),
precompile_runtime,
)
.unwrap();

Expand All @@ -277,6 +298,7 @@ fn test_max_memory_pages(import_memory: bool) {
// Max is 2048.
memory(1, Some(2048), import_memory)
),
precompile_runtime,
)
.unwrap();

Expand Down Expand Up @@ -309,6 +331,7 @@ fn test_max_memory_pages(import_memory: bool) {
// Zero starting pages.
memory(0, None, import_memory)
),
precompile_runtime,
)
.unwrap();

Expand Down Expand Up @@ -341,6 +364,7 @@ fn test_max_memory_pages(import_memory: bool) {
// Initial=1, meaning after heap pages mount the total will be already 1025.
memory(1, None, import_memory)
),
precompile_runtime,
)
.unwrap();
}
Expand All @@ -353,7 +377,6 @@ fn test_instances_without_reuse_are_not_leaked() {
let runtime = crate::create_runtime::<HostFunctions>(
RuntimeBlob::uncompress_if_needed(wasm_binary_unwrap()).unwrap(),
crate::Config {
heap_pages: 2048,
max_memory_size: None,
allow_missing_func_imports: true,
cache_path: None,
Expand All @@ -362,6 +385,7 @@ fn test_instances_without_reuse_are_not_leaked() {
deterministic_stack_limit: None,
canonicalize_nans: false,
parallel_compilation: true,
extra_heap_pages: 2048,
},
},
)
Expand Down