Skip to content

Commit

Permalink
Update to wasmi v0.32 and add lazy validation (#1488)
Browse files Browse the repository at this point in the history
* Try updating to wasmi v0.32

* Use crates.io version

* CHANGELOG

* Add new ExecHints

* Try the git version again

* Fix compilation error due to merge

* crates.io version

* Cargo.lock needs update for some reason
  • Loading branch information
tomaka authored Dec 23, 2023
1 parent ad66d05 commit 3ff5453
Show file tree
Hide file tree
Showing 17 changed files with 111 additions and 27 deletions.
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

0 comments on commit 3ff5453

Please sign in to comment.