Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to wasmi v0.32 and add lazy validation #1488

Merged
merged 10 commits into from
Dec 23, 2023
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
28 changes: 24 additions & 4 deletions Cargo.lock

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

4 changes: 2 additions & 2 deletions full-node/src/consensus_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ impl ConsensusService {
executor::host::HostVmPrototype::new(executor::host::Config {
module: finalized_code,
heap_pages,
exec_hint: executor::vm::ExecHint::CompileAheadOfTime, // TODO: probably should be decided by the optimisticsync
exec_hint: executor::vm::ExecHint::ValidateAndCompile, // TODO: probably should be decided by the optimisticsync
allow_unresolved_imports: false,
})
.map_err(InitError::FinalizedRuntimeInit)?
Expand Down Expand Up @@ -1927,7 +1927,7 @@ impl SyncBackground {
}
all::ProcessOne::WarpSyncBuildRuntime(build_runtime) => {
let (new_sync, outcome) =
build_runtime.build(all::ExecHint::CompileAheadOfTime, true);
build_runtime.build(all::ExecHint::ValidateAndCompile, true);
self.sync = new_sync;
if let Err(err) = outcome {
self.log_callback.log(
Expand Down
2 changes: 1 addition & 1 deletion full-node/src/json_rpc_service/runtime_caches_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ impl RuntimeCachesService {
executor::host::Config {
module: &code,
heap_pages,
exec_hint: executor::vm::ExecHint::CompileAheadOfTime,
exec_hint: executor::vm::ExecHint::ValidateAndCompile,
allow_unresolved_imports: true, // TODO: configurable? or if not, document
},
)
Expand Down
2 changes: 1 addition & 1 deletion full-node/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -853,7 +853,7 @@ async fn open_database(
genesis_storage.value(b":heappages"),
)
.unwrap(),
exec_hint: executor::vm::ExecHint::Oneshot,
exec_hint: executor::vm::ExecHint::ValidateAndExecuteOnce,
allow_unresolved_imports: true,
})
.unwrap()
Expand Down
4 changes: 3 additions & 1 deletion fuzz/fuzz_targets/wasm-module-wasmi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ libfuzzer_sys::fuzz_target!(|data: &[u8]| {
let _ = smoldot::executor::host::HostVmPrototype::new(smoldot::executor::host::Config {
module: data,
heap_pages: smoldot::executor::DEFAULT_HEAP_PAGES,
exec_hint: smoldot::executor::vm::ExecHint::ForceWasmi,
exec_hint: smoldot::executor::vm::ExecHint::ForceWasmi {
lazy_validation: false,
},
allow_unresolved_imports: true,
});
});
2 changes: 1 addition & 1 deletion lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ siphasher = { version = "1.0.0", default-features = false }
slab = { version = "0.4.8", default-features = false }
smallvec = { version = "1.11.0", default-features = false }
twox-hash = { version = "1.6.3", default-features = false }
wasmi = { version = "0.31.0", default-features = false }
wasmi = { version = "0.32.0-beta.2", default-features = false }
x25519-dalek = { version = "2.0.0-rc.3", default-features = false, features = ["alloc", "precomputed-tables", "static_secrets", "zeroize"] }
zeroize = { version = "1.6.0", default-features = false, features = ["alloc"] }

Expand Down
2 changes: 1 addition & 1 deletion lib/src/chain_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ impl ChainSpec {
let vm_prototype = executor::host::HostVmPrototype::new(executor::host::Config {
module: &wasm_code,
heap_pages,
exec_hint: executor::vm::ExecHint::Oneshot,
exec_hint: executor::vm::ExecHint::ValidateAndExecuteOnce,
allow_unresolved_imports: true,
})
.map_err(FromGenesisStorageError::VmInitialization)?;
Expand Down
2 changes: 1 addition & 1 deletion lib/src/executor/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@
//! let prototype = HostVmPrototype::new(Config {
//! module: &wasm_binary_code,
//! heap_pages: HeapPages::from(2048),
//! exec_hint: smoldot::executor::vm::ExecHint::Oneshot,
//! exec_hint: smoldot::executor::vm::ExecHint::ValidateAndExecuteOnce,
//! allow_unresolved_imports: false
//! }).unwrap();
//! prototype.run_no_param("Core_version").unwrap().into()
Expand Down
2 changes: 1 addition & 1 deletion lib/src/executor/runtime_host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1775,7 +1775,7 @@ impl Inner {
let vm_prototype = match host::HostVmPrototype::new(host::Config {
module: req.wasm_code(),
heap_pages: executor::DEFAULT_HEAP_PAGES,
exec_hint: vm::ExecHint::Oneshot,
exec_hint: vm::ExecHint::ValidateAndExecuteOnce,
allow_unresolved_imports: false, // TODO: what is a correct value here?
}) {
Ok(w) => w,
Expand Down
2 changes: 1 addition & 1 deletion lib/src/executor/runtime_host/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ fn execute_blocks() {
host::HostVmPrototype::new(host::Config {
module: code,
heap_pages,
exec_hint: crate::executor::vm::ExecHint::Oneshot,
exec_hint: crate::executor::vm::ExecHint::ExecuteOnceWithNonDeterministicValidation,
allow_unresolved_imports: false,
})
.unwrap()
Expand Down
66 changes: 58 additions & 8 deletions lib/src/executor/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ impl VirtualMachinePrototype {
),
feature = "wasmtime"
))]
ExecHint::CompileAheadOfTime => VirtualMachinePrototypeInner::Jit(
ExecHint::ValidateAndCompile => VirtualMachinePrototypeInner::Jit(
jit::JitPrototype::new(config.module_bytes, config.symbols)?,
),
#[cfg(not(all(
Expand All @@ -159,13 +159,41 @@ impl VirtualMachinePrototype {
),
feature = "wasmtime"
)))]
ExecHint::CompileAheadOfTime => VirtualMachinePrototypeInner::Interpreter(
interpreter::InterpreterPrototype::new(config.module_bytes, config.symbols)?,
ExecHint::ValidateAndCompile => VirtualMachinePrototypeInner::Interpreter(
interpreter::InterpreterPrototype::new(
config.module_bytes,
interpreter::CompilationMode::Eager,
config.symbols,
)?,
),
ExecHint::Oneshot | ExecHint::Untrusted | ExecHint::ForceWasmi => {
ExecHint::ValidateAndExecuteOnce | ExecHint::Untrusted => {
VirtualMachinePrototypeInner::Interpreter(
interpreter::InterpreterPrototype::new(
config.module_bytes,
interpreter::CompilationMode::Eager,
config.symbols,
)?,
)
}
ExecHint::CompileWithNonDeterministicValidation
| ExecHint::ExecuteOnceWithNonDeterministicValidation => {
VirtualMachinePrototypeInner::Interpreter(
interpreter::InterpreterPrototype::new(
config.module_bytes,
interpreter::CompilationMode::Lazy,
config.symbols,
)?,
)
}
ExecHint::ForceWasmi { lazy_validation } => {
VirtualMachinePrototypeInner::Interpreter(
interpreter::InterpreterPrototype::new(
config.module_bytes,
if lazy_validation {
interpreter::CompilationMode::Lazy
} else {
interpreter::CompilationMode::Eager
},
config.symbols,
)?,
)
Expand Down Expand Up @@ -713,18 +741,37 @@ impl fmt::Debug for VirtualMachine {
pub enum ExecHint {
/// The WebAssembly code will be instantiated once and run many times.
/// If possible, compile this WebAssembly code ahead of time.
CompileAheadOfTime,
ValidateAndCompile,

/// The WebAssembly code will be instantiated once and run many times.
/// Contrary to [`ExecHint::ValidateAndCompile`], the WebAssembly code isn't fully validated
/// ahead of time, meaning that invalid WebAssembly modules might successfully be compiled,
/// which is an indesirable property in some contexts.
CompileWithNonDeterministicValidation,

/// The WebAssembly code is expected to be only run once.
///
/// > **Note**: This isn't a hard requirement but a hint.
Oneshot,
ValidateAndExecuteOnce,

/// The WebAssembly code will be instantiated once and run many times.
/// Contrary to [`ExecHint::ValidateAndExecuteOnce`], the WebAssembly code isn't fully
/// validated ahead of time, meaning that invalid WebAssembly modules might successfully be
/// compiled, which is an indesirable property in some contexts.
ExecuteOnceWithNonDeterministicValidation,

/// The WebAssembly code running through this VM is untrusted.
Untrusted,

/// Forces using the `wasmi` backend.
///
/// This variant is useful for testing purposes.
ForceWasmi,
ForceWasmi {
/// If `true`, lazy validation is enabled. This leads to a faster initialization time,
/// but can also successfully validate invalid modules, which is an indesirable property
/// in some contexts.
lazy_validation: bool,
},
/// Forces using the `wasmtime` backend.
///
/// This variant is useful for testing purposes.
Expand Down Expand Up @@ -761,7 +808,10 @@ impl ExecHint {
///
/// > **Note**: This function is most useful for testing purposes.
pub fn available_engines() -> impl Iterator<Item = ExecHint> {
iter::once(ExecHint::ForceWasmi).chain(Self::force_wasmtime_if_available())
iter::once(ExecHint::ForceWasmi {
lazy_validation: false,
})
.chain(Self::force_wasmtime_if_available())
}

/// Returns `ForceWasmtime` if it is available on the current platform, and `None` otherwise.
Expand Down
6 changes: 5 additions & 1 deletion lib/src/executor/vm/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ use super::{
use alloc::{borrow::ToOwned as _, string::ToString as _, sync::Arc, vec::Vec};
use core::fmt;

pub use wasmi::CompilationMode;

/// See [`super::VirtualMachinePrototype`].
pub struct InterpreterPrototype {
/// Base components that can be used to recreate a prototype later if desired.
Expand Down Expand Up @@ -52,6 +54,7 @@ impl InterpreterPrototype {
/// See [`super::VirtualMachinePrototype::new`].
pub fn new(
module_bytes: &[u8],
compilation_mode: CompilationMode,
symbols: &mut dyn FnMut(&str, &str, &Signature) -> Result<usize, ()>,
) -> Result<Self, NewErr> {
let engine = {
Expand All @@ -66,6 +69,7 @@ impl InterpreterPrototype {
config.wasm_mutable_global(false);
config.wasm_saturating_float_to_int(false);
config.wasm_tail_call(false);
config.compilation_mode(compilation_mode);

wasmi::Engine::new(&config)
};
Expand Down Expand Up @@ -129,7 +133,7 @@ impl InterpreterPrototype {
&mut store,
func_type.clone(),
move |_caller, parameters, _ret| {
Err(wasmi::core::Trap::from(InterruptedTrap {
Err(wasmi::Error::host(InterruptedTrap {
function_index,
parameters: parameters
.iter()
Expand Down
2 changes: 1 addition & 1 deletion lib/src/transactions/validate/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ fn validate_from_proof() {
module: hex::decode(&test.runtime_code).unwrap(),
heap_pages: executor::DEFAULT_HEAP_PAGES,
allow_unresolved_imports: true,
exec_hint: executor::vm::ExecHint::Oneshot,
exec_hint: executor::vm::ExecHint::ExecuteOnceWithNonDeterministicValidation,
})
.unwrap();

Expand Down
2 changes: 1 addition & 1 deletion lib/src/verify/body_only.rs
Original file line number Diff line number Diff line change
Expand Up @@ -776,7 +776,7 @@ impl RuntimeCompilation {
let new_runtime = match host::HostVmPrototype::new(host::Config {
module: code,
heap_pages: self.heap_pages,
exec_hint: vm::ExecHint::CompileAheadOfTime,
exec_hint: vm::ExecHint::ValidateAndCompile, // TODO: let API user choose this
allow_unresolved_imports: false,
}) {
Ok(vm) => vm,
Expand Down
5 changes: 4 additions & 1 deletion light-base/src/runtime_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2533,13 +2533,16 @@ impl SuccessfulRuntime {
let module = code.as_ref().ok_or(RuntimeError::CodeNotFound)?;
let heap_pages = executor::storage_heap_pages_to_value(heap_pages.as_deref())
.map_err(RuntimeError::InvalidHeapPages)?;
let exec_hint = executor::vm::ExecHint::CompileAheadOfTime;
// Because the runtime has been validated by at least the author of the block, we assume
// that it is valid. This significantly speeds up the compilation.
let exec_hint = executor::vm::ExecHint::CompileWithNonDeterministicValidation;

// We try once with `allow_unresolved_imports: false`. If this fails due to unresolved
// import, we try again but with `allowed_unresolved_imports: true`.
// Having unresolved imports might cause errors later on, for example when validating
// transactions or getting the parachain heads, but for now we continue the execution
// and print a warning.
// TODO: should log the fact that we're compiling a runtime and the time it takes, as this is a heavy operation
match executor::host::HostVmPrototype::new(executor::host::Config {
module,
heap_pages,
Expand Down
6 changes: 5 additions & 1 deletion light-base/src/sync_service/standalone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1008,7 +1008,11 @@ impl<TPlat: PlatformRef> Task<TPlat> {
// Warp syncing compiles the runtime. The compiled runtime will later be yielded
// in the `WarpSyncFinished` variant, which is then provided as an event.
let before_instant = self.platform.now();
let (new_sync, error) = req.build(all::ExecHint::CompileAheadOfTime, true);
// Because the runtime being compiled has been validated by 2/3rds of the
// validators of the chain, we can assume that it is valid. Doing so significantly
// increases the compilation speed.
let (new_sync, error) =
req.build(all::ExecHint::CompileWithNonDeterministicValidation, true);
let elapsed = self.platform.now() - before_instant;
match error {
Ok(()) => {
Expand Down
1 change: 1 addition & 0 deletions wasm-node/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Changed

- The WebAssembly runtime is now compiled lazily, meaning that only the code that is executed is validated and translated. Thanks to this, compiling a runtime is now four times faster and the time to head of the chain is around 200ms faster. ([#1488](https://github.com/smol-dot/smoldot/pull/1488))
- Decoding and analyzing a Merkle proof is now around 10% to 50% faster. ([#1462](https://github.com/smol-dot/smoldot/pull/1462))

### Fixed
Expand Down
Loading