From 7a40850964bcc1956a7e44da14d625c1bfd10a03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Mon, 23 Oct 2023 17:43:49 +0200 Subject: [PATCH] piecrust: switch from `wasmer` to `wasmtime` We switch WebAssembly runtime - from `wasmer` to `wasmtime` - to make use of the features it has available, notably `memory64` support. In the process we are one step further to having 64-bit contracts, and have gotten rid of the 4 page minimum memory requirement. See-also: #281 --- piecrust/CHANGELOG.md | 28 +++ piecrust/Cargo.toml | 8 +- piecrust/src/contract.rs | 9 +- piecrust/src/error.rs | 64 +----- piecrust/src/imports.rs | 145 +++++++------- piecrust/src/instance.rs | 363 +++++++++++++---------------------- piecrust/src/session.rs | 103 ++++++++-- piecrust/src/store.rs | 5 +- piecrust/src/store/memory.rs | 235 +++++++++-------------- piecrust/src/store/tree.rs | 7 +- piecrust/src/vm.rs | 49 ++++- piecrust/tests/spender.rs | 2 +- 12 files changed, 475 insertions(+), 543 deletions(-) diff --git a/piecrust/CHANGELOG.md b/piecrust/CHANGELOG.md index 8ff3dcae..41c784b0 100644 --- a/piecrust/CHANGELOG.md +++ b/piecrust/CHANGELOG.md @@ -7,6 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## Added + +- Add some `Error` variants: + * `InvalidFunction` + * `InvalidMemory` +- Add `once_cell` dependency + +## Changed + +- Change `Error::RuntimeError` variant to contain `dusk_wasmtime::Error`, + and changed `From` implementation +- Switch runtime from `wasmer` to `wasmtime` + +## Removed + +- Remove 4 page - 256KiB - minimum memory requirement for contracts +- Remove `Clone` derivation for `Error` +- Remove some `Error` variants, along with `From` implementations: + * `CompileError` + * `DeserializeError` + * `ExportError` + * `InstantiationError` + * `InvalidFunctionSignature` + * `MemorySetupError` + * `ParsingError` + * `SerializeError` + * `Trap` + ## [0.11.0] - 2023-10-11 ### Added diff --git a/piecrust/Cargo.toml b/piecrust/Cargo.toml index edebcd70..63624584 100644 --- a/piecrust/Cargo.toml +++ b/piecrust/Cargo.toml @@ -16,13 +16,10 @@ license = "MPL-2.0" crumbles = { version = "0.3", path = "../crumbles" } piecrust-uplink = { version = "0.8", path = "../piecrust-uplink" } -wasmer = "=3.1" -wasmer-vm = "=3.1" -wasmer-types = "=3.1" -wasmer-middlewares = "=3.1" -wasmer-compiler-singlepass = "=3.1" +dusk-wasmtime = { version = "14", default-features = false, features = ["cranelift", "parallel-compilation"] } bytecheck = "0.6" rkyv = { version = "0.7", features = ["size_32", "validation"] } +once_cell = "1.18" parking_lot = "0.12" blake3 = "1" colored = "2" @@ -37,7 +34,6 @@ const-decoder = "0.3" [dev-dependencies] criterion = "0.4" dusk-plonk = { version = "0.14", features = ["rkyv-impl"] } -once_cell = "1.18" [features] debug = [] diff --git a/piecrust/src/contract.rs b/piecrust/src/contract.rs index 1fb1b2a6..ef4e0927 100644 --- a/piecrust/src/contract.rs +++ b/piecrust/src/contract.rs @@ -7,12 +7,11 @@ use std::sync::Arc; use bytecheck::CheckBytes; +use dusk_wasmtime::{Engine, Module}; +use piecrust_uplink::ContractId; use rkyv::{Archive, Deserialize, Serialize}; -use wasmer::Module; use crate::error::Error; -use crate::instance::Store; -use piecrust_uplink::ContractId; pub struct ContractData<'a, A, const N: usize> { pub(crate) contract_id: Option, @@ -89,14 +88,14 @@ pub struct WrappedContract { impl WrappedContract { pub fn new, C: AsRef<[u8]>>( + engine: &Engine, bytecode: B, objectcode: Option, ) -> Result { - let store = Store::new_store(); let serialized = match objectcode { Some(obj) => obj.as_ref().to_vec(), _ => { - let contract = Module::new(&store, bytecode.as_ref())?; + let contract = Module::new(engine, bytecode.as_ref())?; contract.serialize()?.to_vec() } }; diff --git a/piecrust/src/error.rs b/piecrust/src/error.rs index 2d3a44ec..94e2b0e0 100644 --- a/piecrust/src/error.rs +++ b/piecrust/src/error.rs @@ -21,34 +21,28 @@ pub type Compo = CompositeSerializerError< >; /// The error type returned by the piecrust VM. -#[derive(Error, Debug, Clone)] +#[derive(Error, Debug)] pub enum Error { #[error("Commit error: {0}")] CommitError(Cow<'static, str>), #[error(transparent)] - CompileError(Arc), - #[error(transparent)] CompositeSerializerError(Arc), #[error(transparent)] ContractCacheError(Arc), #[error("Contract does not exist: {0}")] ContractDoesNotExist(ContractId), #[error(transparent)] - DeserializeError(Arc), - #[error(transparent)] - ExportError(Arc), - #[error(transparent)] FeedPulled(mpsc::SendError>), #[error(transparent)] Infallible(std::convert::Infallible), #[error("InitalizationError: {0}")] InitalizationError(Cow<'static, str>), - #[error(transparent)] - InstantiationError(Arc), #[error("Invalid global")] InvalidArgumentBuffer, + #[error("Invalid function: {0}")] + InvalidFunction(String), #[error("Invalid memory")] - InvalidFunctionSignature(String), + InvalidMemory, #[error("Memory access out of bounds: offset {offset}, length {len}, memory length {mem_len}")] MemoryAccessOutOfBounds { offset: usize, @@ -60,8 +54,6 @@ pub enum Error { reason: Option>, io: Arc, }, - #[error(transparent)] - MemorySetupError(Arc), #[error("Missing feed")] MissingFeed, #[error("Missing host data: {0}")] @@ -73,21 +65,15 @@ pub enum Error { #[error("Contract panic: {0}")] ContractPanic(String), #[error(transparent)] - ParsingError(wasmer::wasmparser::BinaryReaderError), - #[error(transparent)] PersistenceError(Arc), #[error(transparent)] RestoreError(Arc), #[error(transparent)] - RuntimeError(wasmer::RuntimeError), - #[error(transparent)] - SerializeError(Arc), + RuntimeError(dusk_wasmtime::Error), #[error("Session error: {0}")] SessionError(Cow<'static, str>), #[error("Too many memories: {0}")] TooManyMemories(usize), - #[error("WASMER TRAP")] - Trap(Arc), #[error(transparent)] Utf8(std::str::Utf8Error), #[error("ValidationError")] @@ -118,54 +104,18 @@ impl From for Error { } } -impl From for Error { - fn from(e: wasmer::InstantiationError) -> Self { - Error::InstantiationError(Arc::from(e)) - } -} - -impl From for Error { - fn from(e: wasmer::CompileError) -> Self { - Error::CompileError(Arc::from(e)) - } -} - -impl From for Error { - fn from(e: wasmer::ExportError) -> Self { - Error::ExportError(Arc::from(e)) - } -} - -impl From for Error { - fn from(e: wasmer::RuntimeError) -> Self { +impl From for Error { + fn from(e: dusk_wasmtime::Error) -> Self { Error::RuntimeError(e) } } -impl From for Error { - fn from(e: wasmer::SerializeError) -> Self { - Error::SerializeError(Arc::from(e)) - } -} - -impl From for Error { - fn from(e: wasmer::DeserializeError) -> Self { - Error::DeserializeError(Arc::from(e)) - } -} - impl From for Error { fn from(e: Compo) -> Self { Error::CompositeSerializerError(Arc::from(e)) } } -impl From for Error { - fn from(e: wasmer_vm::Trap) -> Self { - Error::Trap(Arc::from(e)) - } -} - impl From> for Error { fn from(_e: rkyv::validation::CheckArchiveError) -> Self { Error::ValidationError diff --git a/piecrust/src/imports.rs b/piecrust/src/imports.rs index 26c6acf2..8066e97e 100644 --- a/piecrust/src/imports.rs +++ b/piecrust/src/imports.rs @@ -6,47 +6,62 @@ use std::sync::Arc; +use dusk_wasmtime::{ + Caller, Extern, Func, Module, Result as WasmtimeResult, Store, +}; use piecrust_uplink::{ ContractError, ContractId, ARGBUF_LEN, CONTRACT_ID_BYTES, }; -use wasmer::{imports, Function, FunctionEnv, FunctionEnvMut}; use crate::instance::{Env, WrappedInstance}; use crate::Error; const POINT_PASS_PCT: u64 = 93; -pub(crate) struct DefaultImports; - -impl DefaultImports { - pub fn default(store: &mut wasmer::Store, env: Env) -> wasmer::Imports { - let fenv = FunctionEnv::new(store, env); - - #[allow(unused_mut)] - let mut imports = imports! { - "env" => { - "caller" => Function::new_typed_with_env(store, &fenv, caller), - "c" => Function::new_typed_with_env(store, &fenv, c), - "hq" => Function::new_typed_with_env(store, &fenv, hq), - "hd" => Function::new_typed_with_env(store, &fenv, hd), - "emit" => Function::new_typed_with_env(store, &fenv, emit), - "feed" => Function::new_typed_with_env(store, &fenv, feed), - "limit" => Function::new_typed_with_env(store, &fenv, limit), - "spent" => Function::new_typed_with_env(store, &fenv, spent), - "panic" => Function::new_typed_with_env(store, &fenv, panic), - "owner" => Function::new_typed_with_env(store, &fenv, owner), - "self_id" => Function::new_typed_with_env(store, &fenv, self_id), +pub(crate) struct Imports; + +impl Imports { + /// Makes a vector of imports for the given module. + pub fn for_module( + store: &mut Store, + module: &Module, + ) -> Result, Error> { + let max_imports = 12; + let mut imports = Vec::with_capacity(max_imports); + + for import in module.imports() { + let import_name = import.name(); + + match Self::import(store, import_name) { + None => { + return Err(Error::InvalidFunction(import_name.to_string())) + } + Some(func) => { + imports.push(func.into()); + } } - }; + } - #[cfg(feature = "debug")] - imports.define( - "env", - "hdebug", - Function::new_typed_with_env(store, &fenv, hdebug), - ); + Ok(imports) + } - imports + fn import(store: &mut Store, name: &str) -> Option { + Some(match name { + "caller" => Func::wrap(store, caller), + "c" => Func::wrap(store, c), + "hq" => Func::wrap(store, hq), + "hd" => Func::wrap(store, hd), + "emit" => Func::wrap(store, emit), + "feed" => Func::wrap(store, feed), + "limit" => Func::wrap(store, limit), + "spent" => Func::wrap(store, spent), + "panic" => Func::wrap(store, panic), + "owner" => Func::wrap(store, owner), + "self_id" => Func::wrap(store, self_id), + #[cfg(feature = "debug")] + "hdebug" => Func::wrap(store, hdebug), + _ => return None, + }) } } @@ -96,27 +111,27 @@ fn check_arg(instance: &WrappedInstance, arg_len: u32) -> Result<(), Error> { Ok(()) } -fn caller(env: FunctionEnvMut) { +fn caller(env: Caller) { let env = env.data(); let mod_id = env .nth_from_top(1) .map_or(ContractId::uninitialized(), |elem| elem.contract_id); - env.self_instance().with_arg_buffer(|arg| { + env.self_instance().with_arg_buf_mut(|arg| { arg[..std::mem::size_of::()] .copy_from_slice(mod_id.as_bytes()) }) } fn c( - mut fenv: FunctionEnvMut, + mut fenv: Caller, mod_id_ofs: u32, name_ofs: u32, name_len: u32, arg_len: u32, points_limit: u64, -) -> Result { +) -> WasmtimeResult { let env = fenv.data_mut(); let instance = env.self_instance(); @@ -127,9 +142,7 @@ fn c( let argbuf_ofs = instance.arg_buffer_offset(); - let caller_remaining = instance - .get_remaining_points() - .expect("there should be points remaining"); + let caller_remaining = instance.get_remaining_points(); let callee_limit = if points_limit > 0 && points_limit < caller_remaining { points_limit @@ -172,9 +185,7 @@ fn c( // copy back result callee.read_argument(&mut memory[argbuf_ofs..][..ret_len as usize]); - let callee_remaining = callee - .get_remaining_points() - .expect("there should be points remaining"); + let callee_remaining = callee.get_remaining_points(); let callee_spent = callee_limit - callee_remaining; Ok((ret_len, callee_spent)) @@ -204,11 +215,11 @@ fn c( } fn hq( - mut fenv: FunctionEnvMut, + mut fenv: Caller, name_ofs: u32, name_len: u32, arg_len: u32, -) -> Result { +) -> WasmtimeResult { let env = fenv.data_mut(); let instance = env.self_instance(); @@ -225,16 +236,16 @@ fn hq( .map(ToOwned::to_owned) })?; - instance - .with_arg_buffer(|buf| env.host_query(&name, buf, arg_len)) - .ok_or(Error::MissingHostQuery(name)) + Ok(instance + .with_arg_buf_mut(|buf| env.host_query(&name, buf, arg_len)) + .ok_or(Error::MissingHostQuery(name))?) } fn hd( - mut fenv: FunctionEnvMut, + mut fenv: Caller, name_ofs: u32, name_len: u32, -) -> Result { +) -> WasmtimeResult { let env = fenv.data_mut(); let instance = env.self_instance(); @@ -252,7 +263,7 @@ fn hd( let data = env.meta(&name).unwrap_or_default(); - instance.with_arg_buffer(|buf| { + instance.with_arg_buf_mut(|buf| { buf[..data.len()].copy_from_slice(&data); }); @@ -260,18 +271,18 @@ fn hd( } fn emit( - mut fenv: FunctionEnvMut, + mut fenv: Caller, topic_ofs: u32, topic_len: u32, arg_len: u32, -) -> Result<(), Error> { +) -> WasmtimeResult<()> { let env = fenv.data_mut(); let instance = env.self_instance(); check_ptr(instance, topic_ofs, topic_len)?; check_arg(instance, arg_len)?; - let data = instance.with_arg_buffer(|buf| { + let data = instance.with_arg_buf(|buf| { let arg_len = arg_len as usize; Vec::from(&buf[..arg_len]) }); @@ -290,28 +301,28 @@ fn emit( Ok(()) } -fn feed(mut fenv: FunctionEnvMut, arg_len: u32) -> Result<(), Error> { +fn feed(mut fenv: Caller, arg_len: u32) -> WasmtimeResult<()> { let env = fenv.data_mut(); let instance = env.self_instance(); check_arg(instance, arg_len)?; - let data = instance.with_arg_buffer(|buf| { + let data = instance.with_arg_buf(|buf| { let arg_len = arg_len as usize; Vec::from(&buf[..arg_len]) }); - env.push_feed(data) + Ok(env.push_feed(data)?) } #[cfg(feature = "debug")] -fn hdebug(mut fenv: FunctionEnvMut, msg_len: u32) -> Result<(), Error> { +fn hdebug(mut fenv: Caller, msg_len: u32) -> WasmtimeResult<()> { let env = fenv.data_mut(); let instance = env.self_instance(); check_arg(instance, msg_len)?; - instance.with_arg_buffer(|buf| { + Ok(instance.with_arg_buf(|buf| { let slice = &buf[..msg_len as usize]; let msg = match std::str::from_utf8(slice) { @@ -323,32 +334,30 @@ fn hdebug(mut fenv: FunctionEnvMut, msg_len: u32) -> Result<(), Error> { println!("CONTRACT DEBUG {msg}"); Ok(()) - }) + })?) } -fn limit(fenv: FunctionEnvMut) -> u64 { +fn limit(fenv: Caller) -> u64 { fenv.data().limit() } -fn spent(fenv: FunctionEnvMut) -> u64 { +fn spent(fenv: Caller) -> u64 { let env = fenv.data(); let instance = env.self_instance(); let limit = env.limit(); - let remaining = instance - .get_remaining_points() - .expect("there should be remaining points"); + let remaining = instance.get_remaining_points(); limit - remaining } -fn panic(fenv: FunctionEnvMut, arg_len: u32) -> Result<(), Error> { +fn panic(fenv: Caller, arg_len: u32) -> WasmtimeResult<()> { let env = fenv.data(); let instance = env.self_instance(); check_arg(instance, arg_len)?; - instance.with_arg_buffer(|buf| { + Ok(instance.with_arg_buf(|buf| { let slice = &buf[..arg_len as usize]; let msg = match std::str::from_utf8(slice) { @@ -357,10 +366,10 @@ fn panic(fenv: FunctionEnvMut, arg_len: u32) -> Result<(), Error> { }; Err(Error::ContractPanic(msg.to_owned())) - }) + })?) } -fn owner(fenv: FunctionEnvMut) { +fn owner(fenv: Caller) { let env = fenv.data(); let self_id = env.self_contract_id(); let contract_metadata = env @@ -369,10 +378,10 @@ fn owner(fenv: FunctionEnvMut) { let slice = contract_metadata.owner.as_slice(); let len = slice.len(); env.self_instance() - .with_arg_buffer(|arg| arg[..len].copy_from_slice(slice)); + .with_arg_buf_mut(|arg| arg[..len].copy_from_slice(slice)); } -fn self_id(fenv: FunctionEnvMut) { +fn self_id(fenv: Caller) { let env = fenv.data(); let self_id = env.self_contract_id(); let contract_metadata = env @@ -381,5 +390,5 @@ fn self_id(fenv: FunctionEnvMut) { let slice = contract_metadata.contract_id.as_bytes(); let len = slice.len(); env.self_instance() - .with_arg_buffer(|arg| arg[..len].copy_from_slice(slice)); + .with_arg_buf_mut(|arg| arg[..len].copy_from_slice(slice)); } diff --git a/piecrust/src/instance.rs b/piecrust/src/instance.rs index ed14ef4c..c1277e88 100644 --- a/piecrust/src/instance.rs +++ b/piecrust/src/instance.rs @@ -4,36 +4,23 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. +use std::io; use std::ops::{Deref, DerefMut}; -use std::ptr::NonNull; -use std::sync::Arc; -use std::{io, slice}; use colored::*; -use wasmer::wasmparser::Operator; -use wasmer::{CompilerConfig, RuntimeError, Tunables, TypedFunction}; -use wasmer_compiler_singlepass::Singlepass; -use wasmer_middlewares::metering::{ - get_remaining_points, set_remaining_points, MeteringPoints, -}; -use wasmer_middlewares::Metering; -use wasmer_types::{ - MemoryError, MemoryStyle, MemoryType, TableStyle, TableType, -}; -use wasmer_vm::{LinearMemory, VMMemory, VMTable, VMTableDefinition}; - +use dusk_wasmtime::{Instance, Module, Mutability, Store, ValType}; use piecrust_uplink::{ContractId, Event, ARGBUF_LEN}; use crate::contract::WrappedContract; -use crate::imports::DefaultImports; +use crate::imports::Imports; use crate::session::Session; use crate::store::{Memory, MAX_MEM_SIZE}; use crate::Error; pub struct WrappedInstance { - instance: wasmer::Instance, + instance: Instance, arg_buf_ofs: usize, - store: wasmer::Store, + store: Store, memory: Memory, } @@ -95,40 +82,6 @@ impl Env { } } -/// Convenience methods for dealing with our custom store -pub struct Store; - -impl Store { - const INITIAL_POINT_LIMIT: u64 = 10_000_000; - - pub fn new_store() -> wasmer::Store { - Self::with_creator(|compiler_config| { - wasmer::Store::new(compiler_config) - }) - } - - pub fn new_store_with_tunables( - tunables: impl Tunables + Send + Sync + 'static, - ) -> wasmer::Store { - Self::with_creator(|compiler_config| { - wasmer::Store::new_with_tunables(compiler_config, tunables) - }) - } - - fn with_creator(f: F) -> wasmer::Store - where - F: FnOnce(Singlepass) -> wasmer::Store, - { - let metering = - Arc::new(Metering::new(Self::INITIAL_POINT_LIMIT, cost_function)); - - let mut compiler_config = Singlepass::default(); - compiler_config.push_middleware(metering); - - f(compiler_config) - } -} - impl WrappedInstance { pub fn new( session: Session, @@ -136,52 +89,85 @@ impl WrappedInstance { contract: &WrappedContract, memory: Memory, ) -> Result { - let tunables = InstanceTunables::new(memory.clone()); - let mut store = Store::new_store_with_tunables(tunables); + let engine = session.engine().clone(); let env = Env { self_id: contract_id, session, }; - let imports = DefaultImports::default(&mut store, env); - - let module = unsafe { - wasmer::Module::deserialize(&store, contract.as_bytes())? - }; + let module = + unsafe { Module::deserialize(&engine, contract.as_bytes())? }; + let mut store = Store::new(&engine, env); - let instance = wasmer::Instance::new(&mut store, &module, &imports)?; + let imports = Imports::for_module(&mut store, &module)?; + let instance = Instance::new(&mut store, &module, &imports)?; // Ensure there is a global exported named `A`, whose value is in the // memory. - let arg_buf_ofs = - match instance.exports.get_global("A")?.get(&mut store) { - wasmer::Value::I32(i) => i as usize, - _ => return Err(Error::InvalidArgumentBuffer), - }; + let arg_buf_ofs = match instance.get_global(&mut store, "A") { + Some(global) => { + let ty = global.ty(&mut store); + + if ty.mutability() != Mutability::Const { + return Err(Error::InvalidArgumentBuffer); + } + + let val = global.get(&mut store); + + val.i32().ok_or(Error::InvalidArgumentBuffer)? as usize + } + _ => return Err(Error::InvalidArgumentBuffer), + }; + if arg_buf_ofs + ARGBUF_LEN >= MAX_MEM_SIZE { return Err(Error::InvalidArgumentBuffer); } // Ensure there is at most one memory exported, and that it is called // "memory". - let n_memories = instance.exports.iter().memories().count(); + let n_memories = module + .exports() + .filter(|exp| exp.ty().memory().is_some()) + .count(); + if n_memories > 1 { return Err(Error::TooManyMemories(n_memories)); } - let _ = instance.exports.get_memory("memory")?; // Ensure that every exported function has a signature that matches the // calling convention `F: I32 -> I32`. - for (fname, func) in instance.exports.iter().functions() { - let func_ty = func.ty(&store); - if func_ty.params() != [wasmer::Type::I32] - || func_ty.results() != [wasmer::Type::I32] - { - return Err(Error::InvalidFunctionSignature(fname.clone())); + for exp in module.exports() { + let exp_ty = exp.ty(); + if let Some(func_ty) = exp_ty.func() { + let func_name = exp.name(); + + // There must be only one parameter with type `I32`. + let mut params = func_ty.params(); + if params.len() != 1 { + return Err(Error::InvalidFunction(func_name.to_string())); + } + let param = params.next().unwrap(); + if param != ValType::I32 { + return Err(Error::InvalidFunction(func_name.to_string())); + } + + // There must be only one result with type `I32`. + let mut results = func_ty.results(); + if results.len() != 1 { + return Err(Error::InvalidFunction(func_name.to_string())); + } + let result = results.next().unwrap(); + if result != ValType::I32 { + return Err(Error::InvalidFunction(func_name.to_string())); + } } } + let _ = instance + .get_memory(&mut store, "memory") + .ok_or(Error::InvalidMemory)?; + let wrapped = WrappedInstance { store, instance, @@ -192,36 +178,33 @@ impl WrappedInstance { Ok(wrapped) } - pub(crate) fn snap(&self) -> io::Result<()> { - let mut memory = self.memory.write(); - memory.snap()?; + pub(crate) fn snap(&mut self) -> io::Result<()> { + self.memory.snap()?; Ok(()) } - pub(crate) fn revert(&self) -> io::Result<()> { - let mut memory = self.memory.write(); - memory.revert()?; + pub(crate) fn revert(&mut self) -> io::Result<()> { + self.memory.revert()?; Ok(()) } - pub(crate) fn apply(&self) -> io::Result<()> { - let mut memory = self.memory.write(); - memory.apply()?; + pub(crate) fn apply(&mut self) -> io::Result<()> { + self.memory.apply()?; Ok(()) } // Write argument into instance pub(crate) fn write_argument(&mut self, arg: &[u8]) { - self.with_arg_buffer(|buf| buf[..arg.len()].copy_from_slice(arg)) + self.with_arg_buf_mut(|buf| buf[..arg.len()].copy_from_slice(arg)) } // Read argument from instance pub(crate) fn read_argument(&mut self, arg: &mut [u8]) { - self.with_arg_buffer(|buf| arg.copy_from_slice(&buf[..arg.len()])) + self.with_arg_buf(|buf| arg.copy_from_slice(&buf[..arg.len()])) } pub(crate) fn read_bytes_from_arg_buffer(&self, arg_len: u32) -> Vec { - self.with_arg_buffer(|abuf| { + self.with_arg_buf(|abuf| { let slice = &abuf[..arg_len as usize]; slice.to_vec() }) @@ -231,56 +214,48 @@ impl WrappedInstance { where F: FnOnce(&[u8]) -> R, { - let mem = self.instance.exports.get_memory("memory").expect( - "memory export should be checked at contract creation time", - ); - let view = mem.view(&self.store); - let memory_bytes = unsafe { - let slice = view.data_unchecked(); - let ptr = slice.as_ptr(); - slice::from_raw_parts(ptr, MAX_MEM_SIZE) - }; - f(memory_bytes) + f(&self.memory) } - pub(crate) fn with_memory_mut(&self, f: F) -> R + pub(crate) fn with_memory_mut(&mut self, f: F) -> R where F: FnOnce(&mut [u8]) -> R, { - let mem = self.instance.exports.get_memory("memory").expect( - "memory export should be checked at contract creation time", - ); - let view = mem.view(&self.store); - let memory_bytes = unsafe { - let slice = view.data_unchecked_mut(); - let ptr = slice.as_mut_ptr(); - slice::from_raw_parts_mut(ptr, MAX_MEM_SIZE) - }; - f(memory_bytes) + f(&mut self.memory) } /// Returns the current length of the memory. pub(crate) fn mem_len(&self) -> usize { - self.memory.read().inner.def.current_length + self.memory.current_len } /// Sets the length of the memory. pub(crate) fn set_len(&mut self, len: usize) { - self.memory.write().inner.def.current_length = len; + self.memory.current_len = len; + } + + pub(crate) fn with_arg_buf(&self, f: F) -> R + where + F: FnOnce(&[u8]) -> R, + { + let offset = self.arg_buf_ofs; + self.with_memory( + |memory_bytes| f(&memory_bytes[offset..][..ARGBUF_LEN]), + ) } - pub(crate) fn with_arg_buffer(&self, f: F) -> R + pub(crate) fn with_arg_buf_mut(&mut self, f: F) -> R where F: FnOnce(&mut [u8]) -> R, { + let offset = self.arg_buf_ofs; self.with_memory_mut(|memory_bytes| { - let offset = self.arg_buf_ofs; f(&mut memory_bytes[offset..][..ARGBUF_LEN]) }) } - pub(crate) fn write_bytes_to_arg_buffer(&self, buf: &[u8]) -> u32 { - self.with_arg_buffer(|arg_buffer| { + pub(crate) fn write_bytes_to_arg_buffer(&mut self, buf: &[u8]) -> u32 { + self.with_arg_buf_mut(|arg_buffer| { arg_buffer[..buf.len()].copy_from_slice(buf); buf.len() as u32 }) @@ -292,72 +267,70 @@ impl WrappedInstance { arg_len: u32, limit: u64, ) -> Result { - let fun: TypedFunction = self + let fun = self .instance - .exports - .get_typed_function(&self.store, method_name)?; + .get_typed_func::(&mut self.store, method_name)?; self.set_remaining_points(limit); + fun.call(&mut self.store, arg_len) .map_err(|e| map_call_err(self, e)) } pub fn set_remaining_points(&mut self, limit: u64) { - set_remaining_points(&mut self.store, &self.instance, limit); + let remaining = self.store.fuel_remaining().expect("Fuel is enabled"); + self.store + .consume_fuel(remaining) + .expect("Consuming all fuel should succeed"); + self.store + .add_fuel(limit) + .expect("Adding fuel should succeed"); } - pub fn get_remaining_points(&mut self) -> Option { - match get_remaining_points(&mut self.store, &self.instance) { - MeteringPoints::Remaining(points) => Some(points), - MeteringPoints::Exhausted => None, - } + pub fn get_remaining_points(&mut self) -> u64 { + self.store.fuel_remaining().expect("Fuel should be enabled") } - pub fn is_function_exported>(&self, name: N) -> bool { - self.instance.exports.get_function(name.as_ref()).is_ok() + pub fn is_function_exported>(&mut self, name: N) -> bool { + self.instance + .get_func(&mut self.store, name.as_ref()) + .is_some() } #[allow(unused)] pub fn print_state(&self) { - let mem = self - .instance - .exports - .get_memory("memory") - .expect("memory export is checked at contract creation time"); - - let view = mem.view(&self.store); - let maybe_interesting = unsafe { view.data_unchecked_mut() }; - - const CSZ: usize = 128; - const RSZ: usize = 16; - - for (chunk_nr, chunk) in maybe_interesting.chunks(CSZ).enumerate() { - if chunk[..] != [0; CSZ][..] { - for (row_nr, row) in chunk.chunks(RSZ).enumerate() { - let ofs = chunk_nr * CSZ + row_nr * RSZ; - - print!("{ofs:08x}:"); - - for (i, byte) in row.iter().enumerate() { - if i % 4 == 0 { - print!(" "); + self.with_memory(|mem| { + const CSZ: usize = 128; + const RSZ: usize = 16; + + for (chunk_nr, chunk) in mem.chunks(CSZ).enumerate() { + if chunk[..] != [0; CSZ][..] { + for (row_nr, row) in chunk.chunks(RSZ).enumerate() { + let ofs = chunk_nr * CSZ + row_nr * RSZ; + + print!("{ofs:08x}:"); + + for (i, byte) in row.iter().enumerate() { + if i % 4 == 0 { + print!(" "); + } + + let buf_start = self.arg_buf_ofs; + let buf_end = buf_start + ARGBUF_LEN; + + if ofs + i >= buf_start && ofs + i < buf_end { + print!("{}", format!("{byte:02x}").green()); + print!(" "); + } else { + print!("{byte:02x} ") + } } - let buf_start = self.arg_buf_ofs; - let buf_end = buf_start + ARGBUF_LEN; - - if ofs + i >= buf_start && ofs + i < buf_end { - print!("{}", format!("{byte:02x}").green()); - print!(" "); - } else { - print!("{byte:02x} ") - } + println!(); } - - println!(); } } - } + }); } pub fn arg_buffer_offset(&self) -> usize { @@ -365,79 +338,13 @@ impl WrappedInstance { } } -fn map_call_err(instance: &mut WrappedInstance, err: RuntimeError) -> Error { - if instance.get_remaining_points().is_none() { +fn map_call_err( + instance: &mut WrappedInstance, + err: dusk_wasmtime::Error, +) -> Error { + if instance.get_remaining_points() == 0 { return Error::OutOfPoints; } err.into() } - -pub struct InstanceTunables { - memory: Memory, -} - -impl InstanceTunables { - pub fn new(memory: Memory) -> Self { - InstanceTunables { memory } - } -} - -impl Tunables for InstanceTunables { - fn memory_style(&self, _memory: &MemoryType) -> MemoryStyle { - self.memory.style() - } - - fn table_style(&self, _table: &TableType) -> TableStyle { - TableStyle::CallerChecksSignature - } - - fn create_host_memory( - &self, - _ty: &MemoryType, - _style: &MemoryStyle, - ) -> Result { - Ok(VMMemory::from_custom(self.memory.clone())) - } - - unsafe fn create_vm_memory( - &self, - _ty: &MemoryType, - _style: &MemoryStyle, - vm_definition_location: NonNull, - ) -> Result { - // now, it's important to update vm_definition_location with the memory - // information! - let mut ptr = vm_definition_location; - let md = ptr.as_mut(); - - let memory = self.memory.clone(); - - *md = *memory.vmmemory().as_ptr(); - - Ok(memory.into()) - } - - /// Create a table owned by the host given a [`TableType`] and a - /// [`TableStyle`]. - fn create_host_table( - &self, - ty: &TableType, - style: &TableStyle, - ) -> Result { - VMTable::new(ty, style) - } - - unsafe fn create_vm_table( - &self, - ty: &TableType, - style: &TableStyle, - vm_definition_location: NonNull, - ) -> Result { - VMTable::from_definition(ty, style, vm_definition_location) - } -} - -fn cost_function(_op: &Operator) -> u64 { - 1 -} diff --git a/piecrust/src/session.rs b/piecrust/src/session.rs index 25284271..633d413d 100644 --- a/piecrust/src/session.rs +++ b/piecrust/src/session.rs @@ -7,10 +7,13 @@ use std::borrow::Cow; use std::collections::btree_map::Entry; use std::collections::BTreeMap; +use std::fmt::{Debug, Formatter}; use std::mem; use std::sync::{mpsc, Arc}; use bytecheck::CheckBytes; +use dusk_wasmtime::{Config, Engine, LinearMemory, MemoryCreator, MemoryType}; +use once_cell::sync::OnceCell; use piecrust_uplink::{ContractId, Event, ARGBUF_LEN, SCRATCH_BUF_BYTES}; use rkyv::ser::serializers::{ BufferScratch, BufferSerializer, CompositeSerializer, @@ -20,13 +23,12 @@ use rkyv::{ check_archived_root, validation::validators::DefaultValidator, Archive, Deserialize, Infallible, Serialize, }; -use wasmer_types::WASM_PAGE_SIZE; use crate::call_tree::{CallTree, CallTreeElem}; use crate::contract::{ContractData, ContractMetadata, WrappedContract}; use crate::error::Error::{self, InitalizationError, PersistenceError}; use crate::instance::WrappedInstance; -use crate::store::{ContractSession, Objectcode}; +use crate::store::{ContractSession, Objectcode, PAGE_SIZE}; use crate::types::StandardBufSerializer; use crate::vm::HostQueries; @@ -47,19 +49,18 @@ unsafe impl Sync for Session {} /// [`VM`]: crate::VM /// [`call`]: Session::call /// [`commit`]: Session::commit -#[derive(Debug)] pub struct Session { + engine: Engine, inner: &'static mut SessionInner, original: bool, } -/// This implementation purposefully boxes and leaks the `SessionInner`. -impl From for Session { - fn from(inner: SessionInner) -> Self { - Self { - inner: Box::leak(Box::new(inner)), - original: true, - } +impl Debug for Session { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Session") + .field("inner", &self.inner) + .field("original", &self.original) + .finish() } } @@ -81,8 +82,15 @@ impl Drop for Session { } } +fn dummy_engine() -> Engine { + static DUMMY_ENGINE: OnceCell = OnceCell::new(); + DUMMY_ENGINE.get_or_init(Engine::default).clone() +} + #[derive(Debug)] struct SessionInner { + current: ContractId, + call_tree: CallTree, instance_map: BTreeMap, debug: Vec, @@ -96,23 +104,73 @@ struct SessionInner { events: Vec, } +unsafe impl MemoryCreator for Session { + /// This new memory is created for the contract currently at the top of the + /// call tree. + fn new_memory( + &self, + _ty: MemoryType, + minimum: usize, + _maximum: Option, + _reserved_size_in_bytes: Option, + _guard_size_in_bytes: usize, + ) -> Result, String> { + let contract = self.inner.current; + + let session = self.clone(); + + let contract_data = + session.inner.contract_session.contract(contract).map_err( + |err| format!("Failed to get contract from session: {err:?}"), + )?; + + let mut memory = contract_data + .expect("Contract data should exist at this point") + .memory; + + if memory.is_new { + memory.current_len = minimum; + } + + Ok(Box::new(memory)) + } +} + impl Session { pub(crate) fn new( + mut config: Config, contract_session: ContractSession, host_queries: HostQueries, data: SessionData, ) -> Self { - Self::from(SessionInner { + let inner = SessionInner { + current: ContractId::uninitialized(), call_tree: CallTree::new(), instance_map: BTreeMap::new(), debug: vec![], data, contract_session, host_queries, - buffer: vec![0; WASM_PAGE_SIZE], + buffer: vec![0; PAGE_SIZE], feeder: None, events: vec![], - }) + }; + + // This implementation purposefully boxes and leaks the `SessionInner`. + let inner = Box::leak(Box::new(inner)); + + let mut session = Self { + engine: dummy_engine(), + inner, + original: true, + }; + + config.with_host_memory(Arc::new(session.clone())); + + session.engine = Engine::new(&config) + .expect("Engine configuration is set at compile time"); + + session } /// Clone the given session. We explicitly **do not** implement the @@ -127,11 +185,17 @@ impl Session { // SAFETY: we explicitly allow aliasing of the session for internal // use. Self { + engine: self.engine.clone(), inner: unsafe { &mut *inner }, original: false, } } + /// Return a reference to the engine used in this session. + pub(crate) fn engine(&self) -> &Engine { + &self.engine + } + /// Deploy a contract, returning its [`ContractId`]. The ID is computed /// using a `blake3` hash of the `bytecode`. /// @@ -209,7 +273,7 @@ impl Session { } let wrapped_contract = - WrappedContract::new(bytecode, None::)?; + WrappedContract::new(&self.engine, bytecode, None::)?; let contract_metadata = ContractMetadata { contract_id, owner }; let metadata_bytes = Self::serialize_data(&contract_metadata)?; @@ -261,7 +325,7 @@ impl Session { /// /// Calls are atomic, meaning that on failure their execution doesn't modify /// the state. They are also metered, and will execute with the given - /// `points_limit`. + /// `points_limit`. This value should never be 0. /// /// # Errors /// The call may error during execution for a wide array of reasons, the @@ -463,9 +527,13 @@ impl Session { .ok_or(Error::ContractDoesNotExist(contract_id))?; let contract = WrappedContract::new( + &self.engine, store_data.bytecode, Some(store_data.objectcode), )?; + + self.inner.current = contract_id; + let instance = WrappedInstance::new( self.clone(), contract_id, @@ -649,10 +717,7 @@ impl Session { .map_err(Error::normalize)?; let ret = instance.read_bytes_from_arg_buffer(ret_len as u32); - let spent = limit - - instance - .get_remaining_points() - .expect("there should be remaining points"); + let spent = limit - instance.get_remaining_points(); for elem in self.inner.call_tree.iter() { let instance = self diff --git a/piecrust/src/store.rs b/piecrust/src/store.rs index 18a87e30..5eec6c8f 100644 --- a/piecrust/src/store.rs +++ b/piecrust/src/store.rs @@ -20,7 +20,7 @@ use std::sync::mpsc; use std::{fs, io, thread}; pub use bytecode::Bytecode; -pub use memory::{Memory, MAX_MEM_SIZE}; +pub use memory::{Memory, MAX_MEM_SIZE, PAGE_SIZE}; pub use metadata::Metadata; pub use objectcode::Objectcode; use piecrust_uplink::ContractId; @@ -449,14 +449,13 @@ fn write_commit_inner>( for (contract, contract_data) in &commit_contracts { let contract_hex = hex::encode(contract); - let memory = contract_data.memory.read(); let memory_dir = directories.memory_dir.join(&contract_hex); fs::create_dir_all(&memory_dir)?; let mut pages = BTreeSet::new(); - for (dirty_page, _, page_index) in memory.dirty_pages() { + for (dirty_page, _, page_index) in contract_data.memory.dirty_pages() { let page_path = page_path(&memory_dir, *page_index); fs::write(page_path, dirty_page)?; pages.insert(*page_index); diff --git a/piecrust/src/store/memory.rs b/piecrust/src/store/memory.rs index 12f638a1..1f45e500 100644 --- a/piecrust/src/store/memory.rs +++ b/piecrust/src/store/memory.rs @@ -4,33 +4,31 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. +use std::sync::atomic::Ordering; use std::{ fmt::{Debug, Formatter}, io, - ops::{Deref, DerefMut}, - ptr::NonNull, - sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}, + ops::{Deref, DerefMut, Range}, + sync::atomic::AtomicUsize, }; use crumbles::{LocateFile, Mmap}; -use wasmer::WASM_MAX_PAGES; -use wasmer_types::{MemoryType, Pages}; -use wasmer_vm::{ - initialize_memory_with_data, LinearMemory, MemoryError, MemoryStyle, Trap, - VMMemory, VMMemoryDefinition, -}; +use dusk_wasmtime::LinearMemory; + +pub const PAGE_SIZE: usize = 0x10000; +const WASM_MAX_PAGES: u32 = 0x10000; const MIN_PAGES: usize = 4; const MIN_MEM_SIZE: usize = MIN_PAGES * PAGE_SIZE; const MAX_PAGES: usize = WASM_MAX_PAGES as usize; -pub const PAGE_SIZE: usize = wasmer::WASM_PAGE_SIZE; pub const MAX_MEM_SIZE: usize = MAX_PAGES * PAGE_SIZE; -pub(crate) struct MemoryInner { - pub(crate) mmap: Mmap, - pub(crate) def: VMMemoryDefinition, - is_new: bool, +pub struct MemoryInner { + pub mmap: Mmap, + pub current_len: usize, + pub is_new: bool, + ref_count: AtomicUsize, } impl Debug for MemoryInner { @@ -38,189 +36,134 @@ impl Debug for MemoryInner { f.debug_struct("MemoryInner") .field("mmap", &self.mmap.as_ptr()) .field("mmap_len", &self.mmap.len()) - .field("current_len", &self.def.current_length) + .field("current_len", &self.current_len) .field("is_new", &self.is_new) .finish() } } +impl Deref for MemoryInner { + type Target = Mmap; + + fn deref(&self) -> &Self::Target { + &self.mmap + } +} + +impl DerefMut for MemoryInner { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.mmap + } +} + /// WASM memory belonging to a given contract during a given session. -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct Memory { - inner: Arc>, + inner: &'static mut MemoryInner, } impl Memory { - pub(crate) fn new() -> io::Result { - let mut mmap = Mmap::new(MAX_PAGES, PAGE_SIZE)?; - - let def = VMMemoryDefinition { - base: mmap.as_mut_ptr(), - current_length: MIN_MEM_SIZE, - }; - + pub fn new() -> io::Result { Ok(Self { - inner: Arc::new(RwLock::new(MemoryInner { - mmap, - def, + inner: Box::leak(Box::new(MemoryInner { + mmap: Mmap::new(MAX_PAGES, PAGE_SIZE)?, + current_len: MIN_MEM_SIZE, is_new: true, + ref_count: AtomicUsize::new(1), })), }) } - pub(crate) fn from_files( - file_locator: FL, - len: usize, - ) -> io::Result + pub fn from_files(file_locator: FL, len: usize) -> io::Result where FL: 'static + LocateFile, { - let mut mmap = - unsafe { Mmap::with_files(MAX_PAGES, PAGE_SIZE, file_locator)? }; - - let def = VMMemoryDefinition { - base: mmap.as_mut_ptr(), - current_length: len, - }; - Ok(Self { - inner: Arc::new(RwLock::new(MemoryInner { - mmap, - def, + inner: Box::leak(Box::new(MemoryInner { + mmap: unsafe { + Mmap::with_files(MAX_PAGES, PAGE_SIZE, file_locator)? + }, + current_len: len, is_new: false, + ref_count: AtomicUsize::new(1), })), }) } - - pub fn read(&self) -> MemoryReadGuard { - let inner = self.inner.read().unwrap(); - MemoryReadGuard { inner } - } - - pub fn write(&self) -> MemoryWriteGuard { - let inner = self.inner.write().unwrap(); - MemoryWriteGuard { inner } - } -} - -pub struct MemoryReadGuard<'a> { - pub(crate) inner: RwLockReadGuard<'a, MemoryInner>, -} - -impl<'a> AsRef<[u8]> for MemoryReadGuard<'a> { - fn as_ref(&self) -> &[u8] { - &self.inner.mmap - } -} - -impl<'a> Deref for MemoryReadGuard<'a> { - type Target = Mmap; - - fn deref(&self) -> &Self::Target { - &self.inner.mmap - } -} - -pub struct MemoryWriteGuard<'a> { - pub(crate) inner: RwLockWriteGuard<'a, MemoryInner>, } -impl<'a> AsRef<[u8]> for MemoryWriteGuard<'a> { - fn as_ref(&self) -> &[u8] { - &self.inner.mmap +/// This implementation of clone is dangerous, and must be accompanied by the +/// underneath implementation of `Drop`. +/// +/// We do this to avoid locking the memory in any way when recursively offering +/// it to a session. +/// +/// It is safe since we guarantee that there is no access contention - read or +/// write. +impl Clone for Memory { + fn clone(&self) -> Self { + self.ref_count.fetch_add(1, Ordering::SeqCst); + + let inner = self.inner as *const MemoryInner; + let inner = inner as *mut MemoryInner; + // SAFETY: we explicitly allow aliasing of the memory for internal + // use. + Self { + inner: unsafe { &mut *inner }, + } } } -impl<'a> AsMut<[u8]> for MemoryWriteGuard<'a> { - fn as_mut(&mut self) -> &mut [u8] { - &mut self.inner.mmap +impl Drop for Memory { + fn drop(&mut self) { + if self.ref_count.fetch_sub(1, Ordering::SeqCst) == 1 { + unsafe { + let _ = Box::from_raw(self.inner); + } + } } } -impl<'a> Deref for MemoryWriteGuard<'a> { - type Target = Mmap; +impl Deref for Memory { + type Target = MemoryInner; fn deref(&self) -> &Self::Target { - &self.inner.mmap + self.inner } } -impl<'a> DerefMut for MemoryWriteGuard<'a> { +impl DerefMut for Memory { fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner.mmap + self.inner } } -impl LinearMemory for Memory { - fn ty(&self) -> MemoryType { - MemoryType { - minimum: Pages(MIN_PAGES as u32), - maximum: Some(Pages(MAX_PAGES as u32)), - shared: false, - } - } - - fn size(&self) -> Pages { - let pages = self.read().inner.def.current_length / PAGE_SIZE; - Pages(pages as u32) +unsafe impl LinearMemory for Memory { + fn byte_size(&self) -> usize { + self.inner.current_len } - fn style(&self) -> MemoryStyle { - MemoryStyle::Static { - bound: Pages(MAX_PAGES as u32), - offset_guard_size: 0, - } + fn maximum_byte_size(&self) -> Option { + Some(MAX_MEM_SIZE) } - fn grow(&mut self, delta: Pages) -> Result { - let mut memory = self.write(); - - let current_len = memory.inner.def.current_length; - let new_len = current_len + delta.0 as usize * PAGE_SIZE; - - if new_len > MAX_PAGES * PAGE_SIZE { - return Err(MemoryError::CouldNotGrow { - current: Pages((current_len / PAGE_SIZE) as u32), - attempted_delta: delta, - }); - } - - memory.inner.def = VMMemoryDefinition { - base: memory.as_mut_ptr(), - current_length: new_len, - }; - - Ok(Pages((new_len / PAGE_SIZE) as u32)) + fn grow_to(&mut self, new_size: usize) -> Result<(), dusk_wasmtime::Error> { + self.inner.current_len = new_size; + Ok(()) } - fn vmmemory(&self) -> NonNull { - let inner = self.inner.read().unwrap(); - let ptr = &inner.def as *const VMMemoryDefinition; - NonNull::new(ptr as *mut VMMemoryDefinition).unwrap() + fn needs_init(&self) -> bool { + self.is_new } - fn try_clone(&self) -> Option> { - Some(Box::new(self.clone())) + fn as_ptr(&self) -> *mut u8 { + self.inner.as_ptr() as _ } - unsafe fn initialize_with_data( - &self, - start: usize, - data: &[u8], - ) -> Result<(), Trap> { - let this = self.write(); - let inner = this.inner; - - if inner.is_new { - initialize_memory_with_data(&inner.def, start, data)?; - } - - Ok(()) - } -} + fn wasm_accessible(&self) -> Range { + let begin = self.inner.mmap.as_ptr() as _; + let len = self.inner.current_len; + let end = begin + len; -impl From for VMMemory { - fn from(memory: Memory) -> Self { - VMMemory(Box::new(memory)) + begin..end } } diff --git a/piecrust/src/store/tree.rs b/piecrust/src/store/tree.rs index e0ef8fef..48f63b2c 100644 --- a/piecrust/src/store/tree.rs +++ b/piecrust/src/store/tree.rs @@ -62,12 +62,9 @@ impl ContractIndex { } let element = self.contracts.get_mut(&contract).unwrap(); - let memory = memory.read(); - let memory_inner = memory.inner; + element.len = memory.current_len; - element.len = memory_inner.def.current_length; - - for (dirty_page, _, page_index) in memory_inner.mmap.dirty_pages() { + for (dirty_page, _, page_index) in memory.dirty_pages() { element.page_indices.insert(*page_index); let hash = Hash::new(dirty_page); element.tree.insert(*page_index as u64, hash); diff --git a/piecrust/src/vm.rs b/piecrust/src/vm.rs index 7cff397d..d6c02aa1 100644 --- a/piecrust/src/vm.rs +++ b/piecrust/src/vm.rs @@ -11,13 +11,52 @@ use std::path::Path; use std::sync::Arc; use std::thread; +use dusk_wasmtime::{ + Config, ModuleVersionStrategy, OptLevel, Strategy, WasmBacktraceDetails, +}; use tempfile::tempdir; -use wasmer_vm::init_traps; use crate::session::{Session, SessionData}; use crate::store::ContractStore; use crate::Error::{self, PersistenceError}; +fn config() -> Config { + let mut config = Config::new(); + + // Neither WASM backtrace, nor native unwind info. + config.wasm_backtrace(false); + config.wasm_backtrace_details(WasmBacktraceDetails::Disable); + + config.native_unwind_info(false); + + // 512KiB of max stack is the default, but we want to be explicit about it. + config.max_wasm_stack(0x80000); + config.consume_fuel(true); + + config.strategy(Strategy::Cranelift); + config.cranelift_opt_level(OptLevel::SpeedAndSize); + // We need entirely deterministic computation + config.cranelift_nan_canonicalization(true); + + // Host memory creator is set in the session. + // config.with_host_memory() + + config.static_memory_forced(true); + config.static_memory_maximum_size(0x100000000); + config.static_memory_guard_size(0); + config.dynamic_memory_guard_size(0); + config.guard_before_linear_memory(false); + config.memory_init_cow(false); + + config + .module_version(ModuleVersionStrategy::Custom(String::from("piecrust"))) + .expect("Module version should be valid"); + config.generate_address_map(false); + config.macos_use_mach_ports(false); + + config +} + /// A handle to the piecrust virtual machine. /// /// It is instantiated using [`new`] or [`ephemeral`], and can be used to spawn @@ -38,6 +77,7 @@ use crate::Error::{self, PersistenceError}; /// [`sync_thread`]: VM::sync_thread #[derive(Debug)] pub struct VM { + config: Config, host_queries: HostQueries, store: ContractStore, } @@ -52,11 +92,10 @@ impl VM { /// # Errors /// If the directory contains unparseable or inconsistent data. pub fn new>(root_dir: P) -> Result { - init_traps(); - let store = ContractStore::new(root_dir) .map_err(|err| PersistenceError(Arc::new(err)))?; Ok(Self { + config: config(), host_queries: HostQueries::default(), store, }) @@ -70,8 +109,6 @@ impl VM { /// # Errors /// If creating a temporary directory fails. pub fn ephemeral() -> Result { - init_traps(); - let tmp = tempdir().map_err(|err| PersistenceError(Arc::new(err)))?; let tmp = tmp.path().to_path_buf(); @@ -79,6 +116,7 @@ impl VM { .map_err(|err| PersistenceError(Arc::new(err)))?; Ok(Self { + config: config(), host_queries: HostQueries::default(), store, }) @@ -117,6 +155,7 @@ impl VM { _ => self.store.genesis_session(), }; Ok(Session::new( + self.config.clone(), contract_session, self.host_queries.clone(), data, diff --git a/piecrust/tests/spender.rs b/piecrust/tests/spender.rs index 63d6ab2f..466de100 100644 --- a/piecrust/tests/spender.rs +++ b/piecrust/tests/spender.rs @@ -57,7 +57,7 @@ pub fn fails_with_out_of_points() -> Result<(), Error> { )?; let err = session - .call::<_, i64>(counter_id, "read_value", &(), 0) + .call::<_, i64>(counter_id, "read_value", &(), 1) .expect_err("should error with no gas"); assert!(matches!(err, Error::OutOfPoints));