sc-executor-wasmtime: upgrade wasmtime to 0.30.0 (#10003)

* sc-executor-wasmtime: upgrade wasmtime to 0.30.0

Changes related to memory64 proposal implementation,
for additional details see bytecodealliance/wasmtime#3153

* sc-executor-wasmtime: introduce parallel_compilation flag

* typos
This commit is contained in:
Chris Sosnin
2021-10-12 18:15:23 +03:00
committed by GitHub
parent a56d74a23b
commit 806b426a8f
7 changed files with 145 additions and 177 deletions
@@ -23,8 +23,9 @@ sp-wasm-interface = { version = "4.0.0-dev", path = "../../../primitives/wasm-in
sp-runtime-interface = { version = "4.0.0-dev", path = "../../../primitives/runtime-interface" }
sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" }
sc-allocator = { version = "4.0.0-dev", path = "../../allocator" }
wasmtime = { version = "0.29.0", default-features = false, features = [
wasmtime = { version = "0.30.0", default-features = false, features = [
"cache",
"cranelift",
"jitdump",
"parallel-compilation",
] }
@@ -22,10 +22,9 @@ use crate::{
};
use sc_executor_common::error::WasmError;
use sp_wasm_interface::{Function, ValueType};
use std::any::Any;
use std::{any::Any, convert::TryInto};
use wasmtime::{
Caller, Extern, ExternType, Func, FuncType, ImportType, Limits, Memory, MemoryType, Module,
Trap, Val,
Caller, Extern, ExternType, Func, FuncType, ImportType, Memory, MemoryType, Module, Trap, Val,
};
pub struct Imports {
@@ -41,7 +40,7 @@ pub(crate) fn resolve_imports(
store: &mut Store,
module: &Module,
host_functions: &[&'static dyn Function],
heap_pages: u32,
heap_pages: u64,
allow_missing_func_imports: bool,
) -> Result<Imports, WasmError> {
let mut externs = vec![];
@@ -83,7 +82,7 @@ fn import_name<'a, 'b: 'a>(import: &'a ImportType<'b>) -> Result<&'a str, WasmEr
fn resolve_memory_import(
store: &mut Store,
import_ty: &ImportType,
heap_pages: u32,
heap_pages: u64,
) -> Result<Extern, WasmError> {
let requested_memory_ty = match import_ty.ty() {
ExternType::Memory(memory_ty) => memory_ty,
@@ -97,8 +96,8 @@ fn resolve_memory_import(
// Increment the min (a.k.a initial) number of pages by `heap_pages` and check if it exceeds the
// maximum specified by the import.
let initial = requested_memory_ty.limits().min().saturating_add(heap_pages);
if let Some(max) = requested_memory_ty.limits().max() {
let initial = requested_memory_ty.minimum().saturating_add(heap_pages);
if let Some(max) = requested_memory_ty.maximum() {
if initial > max {
return Err(WasmError::Other(format!(
"incremented number of pages by heap_pages (total={}) is more than maximum requested\
@@ -109,7 +108,27 @@ fn resolve_memory_import(
}
}
let memory_ty = MemoryType::new(Limits::new(initial, requested_memory_ty.limits().max()));
// Note that the return value of `maximum` and `minimum`, while a u64,
// will always fit into a u32 for 32-bit memories.
// 64-bit memories are part of the memory64 proposal for WebAssembly which is not standardized
// yet.
let minimum: u32 = initial.try_into().map_err(|_| {
WasmError::Other(format!(
"minimum number of memory pages ({}) doesn't fit into u32",
initial
))
})?;
let maximum: Option<u32> = match requested_memory_ty.maximum() {
Some(max) => Some(max.try_into().map_err(|_| {
WasmError::Other(format!(
"maximum number of memory pages ({}) doesn't fit into u32",
max
))
})?),
None => None,
};
let memory_ty = MemoryType::new(minimum, maximum);
let memory = Memory::new(store, memory_ty).map_err(|e| {
WasmError::Other(format!(
"failed to create a memory during resolving of memory import: {}",
@@ -154,7 +154,7 @@ impl InstanceWrapper {
pub fn new(
module: &Module,
imports: &Imports,
heap_pages: u32,
heap_pages: u64,
mut ctx: impl AsContextMut,
) -> Result<Self> {
let instance = Instance::new(&mut ctx, module, &imports.externs)
@@ -77,7 +77,7 @@ struct InstanceCreator {
store: Store,
module: Arc<wasmtime::Module>,
imports: Arc<Imports>,
heap_pages: u32,
heap_pages: u64,
}
impl InstanceCreator {
@@ -130,15 +130,15 @@ pub struct WasmtimeRuntime {
impl WasmtimeRuntime {
/// Creates the store respecting the set limits.
fn new_store(&self) -> Store {
let limits = if let Some(max_memory_pages) = self.config.max_memory_pages {
wasmtime::StoreLimitsBuilder::new().memory_pages(max_memory_pages).build()
let limits = if let Some(max_memory_size) = self.config.max_memory_size {
wasmtime::StoreLimitsBuilder::new().memory_size(max_memory_size).build()
} else {
Default::default()
};
let mut store = Store::new(&self.engine, StoreData { limits, host_state: None });
if self.config.max_memory_pages.is_some() {
if self.config.max_memory_size.is_some() {
store.limiter(|s| &mut s.limits);
}
@@ -350,6 +350,8 @@ fn common_config(semantics: &Semantics) -> std::result::Result<wasmtime::Config,
.map_err(|e| WasmError::Other(format!("cannot set max wasm stack: {}", e)))?;
}
config.parallel_compilation(semantics.parallel_compilation);
// Be clear and specific about the extensions we support. If an update brings new features
// they should be introduced here as well.
config.wasm_reference_types(false);
@@ -381,7 +383,7 @@ fn common_config(semantics: &Semantics) -> std::result::Result<wasmtime::Config,
/// usage in bytes.
///
/// The actual number of bytes consumed by a function is not trivial to compute without going
/// through full compilation. Therefore, it's expected that `native_stack_max` is grealy
/// through full compilation. Therefore, it's expected that `native_stack_max` is greatly
/// overestimated and thus never reached in practice. The stack overflow check introduced by the
/// instrumentation and that relies on the logical item count should be reached first.
///
@@ -399,7 +401,7 @@ pub struct DeterministicStackLimit {
/// It's not specified how much bytes will be consumed by a stack frame for a given wasm
/// function after translation into machine code. It is also not quite trivial.
///
/// Therefore, this number should be choosen conservatively. It must be so large so that it can
/// Therefore, this number should be chosen conservatively. It must be so large so that it can
/// fit the [`logical_max`](Self::logical_max) logical values on the stack, according to the
/// current instrumentation algorithm.
///
@@ -409,7 +411,7 @@ pub struct DeterministicStackLimit {
pub struct Semantics {
/// Enabling this will lead to some optimization shenanigans that make calling [`WasmInstance`]
/// extermely fast.
/// extremely fast.
///
/// Primarily this is achieved by not recreating the instance for each call and performing a
/// bare minimum clean up: reapplying the data segments and restoring the values for global
@@ -449,28 +451,32 @@ pub struct Semantics {
/// developers. For PVFs, we want to ensure that execution is deterministic though. Therefore,
/// for PVF execution this flag is meant to be turned on.
pub canonicalize_nans: bool,
/// Configures wasmtime to use multiple threads for compiling.
pub parallel_compilation: bool,
}
pub struct Config {
/// The number of wasm pages to be mounted after instantiation.
pub heap_pages: u32,
pub heap_pages: u64,
/// The total number of wasm pages an instance can request.
/// 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 pages.
/// If specified, the runtime will be able to allocate only that much of wasm memory.
/// This is the total number and therefore the [`heap_pages`] is accounted for.
///
/// That means that the initial number of pages of a linear memory plus the [`heap_pages`]
/// should be less or equal to `max_memory_pages`, otherwise the instance won't be created.
/// 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 the number of currently mounted
/// pages and the number of additional pages exceeds `max_memory_pages`.
/// Moreover, `memory.grow` will fail (return -1) if the sum of sizes of currently mounted
/// and additional pages exceeds `max_memory_size`.
///
/// The default is `None`.
pub max_memory_pages: Option<u32>,
pub max_memory_size: Option<usize>,
/// The WebAssembly standard requires all imports of an instantiated module to be resolved,
/// othewise, the instantiation fails. If this option is set to `true`, then this behavior is
/// otherwise, the instantiation fails. If this option is set to `true`, then this behavior is
/// overriden and imports that are requested by the module and not provided by the host
/// functions will be resolved using stubs. These stubs will trap upon a call.
pub allow_missing_func_imports: bool,
+16 -13
View File
@@ -28,8 +28,8 @@ struct RuntimeBuilder {
fast_instance_reuse: bool,
canonicalize_nans: bool,
deterministic_stack: bool,
heap_pages: u32,
max_memory_pages: Option<u32>,
heap_pages: u64,
max_memory_size: Option<usize>,
}
impl RuntimeBuilder {
@@ -42,7 +42,7 @@ impl RuntimeBuilder {
canonicalize_nans: false,
deterministic_stack: false,
heap_pages: 1024,
max_memory_pages: None,
max_memory_size: None,
}
}
@@ -58,8 +58,8 @@ impl RuntimeBuilder {
self.deterministic_stack = deterministic_stack;
}
fn max_memory_pages(&mut self, max_memory_pages: Option<u32>) {
self.max_memory_pages = max_memory_pages;
fn max_memory_size(&mut self, max_memory_size: Option<usize>) {
self.max_memory_size = max_memory_size;
}
fn build(self) -> Arc<dyn WasmModule> {
@@ -82,7 +82,7 @@ impl RuntimeBuilder {
blob,
crate::Config {
heap_pages: self.heap_pages,
max_memory_pages: self.max_memory_pages,
max_memory_size: self.max_memory_size,
allow_missing_func_imports: true,
cache_path: None,
semantics: crate::Semantics {
@@ -95,6 +95,7 @@ impl RuntimeBuilder {
false => None,
},
canonicalize_nans: self.canonicalize_nans,
parallel_compilation: true,
},
},
{
@@ -171,13 +172,13 @@ fn test_stack_depth_reaching() {
#[test]
fn test_max_memory_pages() {
fn try_instantiate(
max_memory_pages: Option<u32>,
max_memory_size: Option<usize>,
wat: &'static str,
) -> Result<(), Box<dyn std::error::Error>> {
let runtime = {
let mut builder = RuntimeBuilder::new_on_demand();
builder.use_wat(wat);
builder.max_memory_pages(max_memory_pages);
builder.max_memory_size(max_memory_size);
builder.build()
};
let mut instance = runtime.new_instance()?;
@@ -185,6 +186,8 @@ fn test_max_memory_pages() {
Ok(())
}
const WASM_PAGE_SIZE: usize = 65536;
// check the old behavior if preserved. That is, if no limit is set we allow 4 GiB of memory.
try_instantiate(
None,
@@ -213,9 +216,9 @@ fn test_max_memory_pages() {
// max is not specified, therefore it's implied to be 65536 pages (4 GiB).
//
// max_memory_pages = 1 (initial) + 1024 (heap_pages)
// max_memory_size = (1 (initial) + 1024 (heap_pages)) * WASM_PAGE_SIZE
try_instantiate(
Some(1 + 1024),
Some((1 + 1024) * WASM_PAGE_SIZE),
r#"
(module
@@ -233,7 +236,7 @@ fn test_max_memory_pages() {
// max is specified explicitly to 2048 pages.
try_instantiate(
Some(1 + 1024),
Some((1 + 1024) * WASM_PAGE_SIZE),
r#"
(module
@@ -251,7 +254,7 @@ fn test_max_memory_pages() {
// memory grow should work as long as it doesn't exceed 1025 pages in total.
try_instantiate(
Some(0 + 1024 + 25),
Some((0 + 1024 + 25) * WASM_PAGE_SIZE),
r#"
(module
(import "env" "memory" (memory 0)) ;; <- zero starting pages.
@@ -280,7 +283,7 @@ fn test_max_memory_pages() {
// We start with 1025 pages and try to grow at least one.
try_instantiate(
Some(1 + 1024),
Some((1 + 1024) * WASM_PAGE_SIZE),
r#"
(module
(import "env" "memory" (memory 1)) ;; <- initial=1, meaning after heap pages mount the