Skip to content

Commit

Permalink
Add wasmtime support and make wasmi optional.
Browse files Browse the repository at this point in the history
  • Loading branch information
graydon committed Jan 10, 2025
1 parent ad0d6d2 commit 976fa5d
Show file tree
Hide file tree
Showing 21 changed files with 1,514 additions and 101 deletions.
639 changes: 603 additions & 36 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ soroban-builtin-sdk-macros = { version = "=22.1.3", path = "soroban-builtin-sdk-
# NB: this must match the wasmparser version wasmi is using
wasmparser = "=0.116.1"

[workspace.dependencies.wasmtime]
version = "28.0"
default-features = false
features = ["runtime", "winch"]
git = "https://github.com/bytecodealliance/wasmtime"
rev = "b5627a86a7740ffc732f4c22b9f0b2c66252638b"

# NB: When updating, also update the version in rs-soroban-env dev-dependencies
[workspace.dependencies.stellar-xdr]
version = "=22.1.0"
Expand Down
2 changes: 2 additions & 0 deletions soroban-env-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ ethnum = "1.5.0"
arbitrary = { version = "1.3.2", features = ["derive"], optional = true }
num-traits = {version = "0.2.17", default-features = false}
num-derive = "0.4.1"
wasmtime = { workspace = true, optional = true}

[target.'cfg(not(target_family = "wasm"))'.dependencies]
tracy-client = { version = "0.17.0", features = ["enable", "timer-fallback"], default-features = false, optional = true }
Expand All @@ -36,6 +37,7 @@ num-traits = "0.2.17"
std = ["stellar-xdr/std", "stellar-xdr/base64"]
serde = ["dep:serde", "stellar-xdr/serde"]
wasmi = ["dep:wasmi", "dep:wasmparser"]
wasmtime = ["dep:wasmtime"]
testutils = ["dep:arbitrary", "stellar-xdr/arbitrary"]
next = ["stellar-xdr/next", "soroban-env-macros/next"]
tracy = ["dep:tracy-client"]
Expand Down
43 changes: 43 additions & 0 deletions soroban-env-common/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,49 @@ impl From<wasmparser::BinaryReaderError> for Error {
}
}

#[cfg(feature = "wasmtime")]
impl From<wasmtime::Trap> for Error {
#[allow(clippy::wildcard_in_or_patterns)]
fn from(trap: wasmtime::Trap) -> Self {
let ec = match trap {
wasmtime::Trap::UnreachableCodeReached => ScErrorCode::InvalidAction,

wasmtime::Trap::MemoryOutOfBounds | wasmtime::Trap::TableOutOfBounds => {
ScErrorCode::IndexBounds
}

wasmtime::Trap::IndirectCallToNull => ScErrorCode::MissingValue,

wasmtime::Trap::IntegerDivisionByZero
| wasmtime::Trap::IntegerOverflow
| wasmtime::Trap::BadConversionToInteger => ScErrorCode::ArithDomain,

wasmtime::Trap::BadSignature => ScErrorCode::UnexpectedType,

wasmtime::Trap::StackOverflow
| wasmtime::Trap::Interrupt
| wasmtime::Trap::OutOfFuel => {
return Error::from_type_and_code(ScErrorType::Budget, ScErrorCode::ExceededLimit)
}

wasmtime::Trap::HeapMisaligned
| wasmtime::Trap::AlwaysTrapAdapter
| wasmtime::Trap::AtomicWaitNonSharedMemory
| wasmtime::Trap::NullReference
| wasmtime::Trap::CannotEnterComponent
| _ => ScErrorCode::InvalidAction,
};
Error::from_type_and_code(ScErrorType::WasmVm, ec)
}
}

#[cfg(feature = "wasmtime")]
impl From<wasmtime::MemoryAccessError> for Error {
fn from(_: wasmtime::MemoryAccessError) -> Self {
Error::from_type_and_code(ScErrorType::WasmVm, ScErrorCode::IndexBounds)
}
}

impl Error {
// NB: we don't provide a "get_type" to avoid casting a bad bit-pattern into
// an ScErrorType. Instead we provide an "is_type" to check any specific
Expand Down
2 changes: 2 additions & 0 deletions soroban-env-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ pub use val::{ConversionError, Tag, Val};

#[cfg(feature = "wasmi")]
pub use val::WasmiMarshal;
#[cfg(feature = "wasmtime")]
pub use val::WasmtimeMarshal;
pub use val::{AddressObject, MapObject, VecObject};
pub use val::{Bool, Void};

Expand Down
56 changes: 56 additions & 0 deletions soroban-env-common/src/val.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,62 @@ impl WasmiMarshal for i64 {
}
}

#[cfg(feature = "wasmtime")]
pub trait WasmtimeMarshal: Sized {
fn try_marshal_from_wasmtime_value(v: wasmtime::Val) -> Option<Self>;
fn marshal_wasmtime_from_self(self) -> wasmtime::Val;
}

#[cfg(feature = "wasmtime")]
impl WasmtimeMarshal for Val {
fn try_marshal_from_wasmtime_value(v: wasmtime::Val) -> Option<Self> {
if let wasmtime::Val::I64(i) = v {
let v = Val::from_payload(i as u64);
if v.is_good() {
Some(v)
} else {
None
}
} else {
None
}
}

fn marshal_wasmtime_from_self(self) -> wasmtime::Val {
wasmtime::Val::I64(self.get_payload() as i64)
}
}

#[cfg(feature = "wasmtime")]
impl WasmtimeMarshal for u64 {
fn try_marshal_from_wasmtime_value(v: wasmtime::Val) -> Option<Self> {
if let wasmtime::Val::I64(i) = v {
Some(i as u64)
} else {
None
}
}

fn marshal_wasmtime_from_self(self) -> wasmtime::Val {
wasmtime::Val::I64(self as i64)
}
}

#[cfg(feature = "wasmtime")]
impl WasmtimeMarshal for i64 {
fn try_marshal_from_wasmtime_value(v: wasmtime::Val) -> Option<Self> {
if let wasmtime::Val::I64(i) = v {
Some(i)
} else {
None
}
}

fn marshal_wasmtime_from_self(self) -> wasmtime::Val {
wasmtime::Val::I64(self)
}
}

// Manually implement all the residual pieces: ValConverts
// and Froms.

Expand Down
53 changes: 41 additions & 12 deletions soroban-env-common/src/vmcaller_env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,38 +25,67 @@ use core::marker::PhantomData;
/// allows code to import and use `Env` directly (such as the native
/// contract) to call host methods without having to write `VmCaller::none()`
/// everywhere.
#[cfg(feature = "wasmi")]
pub struct VmCaller<'a, T>(pub Option<wasmi::Caller<'a, T>>);
#[cfg(feature = "wasmi")]
#[cfg(any(feature = "wasmi", feature = "wasmtime"))]
pub enum VmCaller<'a, T> {
#[cfg(feature = "wasmi")]
WasmiCaller(wasmi::Caller<'a, T>),
#[cfg(feature = "wasmtime")]
WasmtimeCaller(wasmtime::Caller<'a, T>),
NoCaller,
}
#[cfg(any(feature = "wasmi", feature = "wasmtime"))]
impl<'a, T> VmCaller<'a, T> {
pub fn none() -> Self {
VmCaller(None)
VmCaller::NoCaller
}
#[cfg(feature = "wasmi")]
pub fn try_ref(&self) -> Result<&wasmi::Caller<'a, T>, Error> {
match &self.0 {
Some(caller) => Ok(caller),
None => Err(Error::from_type_and_code(
match self {
VmCaller::WasmiCaller(caller) => Ok(caller),
_ => Err(Error::from_type_and_code(
ScErrorType::Context,
ScErrorCode::InternalError,
)),
}
}
#[cfg(feature = "wasmi")]
pub fn try_mut(&mut self) -> Result<&mut wasmi::Caller<'a, T>, Error> {
match &mut self.0 {
Some(caller) => Ok(caller),
None => Err(Error::from_type_and_code(
match self {
VmCaller::WasmiCaller(caller) => Ok(caller),
_ => Err(Error::from_type_and_code(
ScErrorType::Context,
ScErrorCode::InternalError,
)),
}
}
#[cfg(feature = "wasmtime")]
pub fn try_ref_wasmtime(&self) -> Result<&wasmtime::Caller<'a, T>, Error> {
match self {
VmCaller::WasmtimeCaller(caller) => Ok(caller),
_ => Err(Error::from_type_and_code(
ScErrorType::Context,
ScErrorCode::InternalError,
)),
}
}
#[cfg(feature = "wasmtime")]
pub fn try_mut_wasmtime(&mut self) -> Result<&mut wasmtime::Caller<'a, T>, Error> {
match self {
VmCaller::WasmtimeCaller(caller) => Ok(caller),
_ => Err(Error::from_type_and_code(
ScErrorType::Context,
ScErrorCode::InternalError,
)),
}
}
}

#[cfg(not(feature = "wasmi"))]
#[cfg(not(any(feature = "wasmi", feature = "wasmtime")))]
pub struct VmCaller<'a, T> {
_nothing: PhantomData<&'a T>,
}
#[cfg(not(feature = "wasmi"))]
#[cfg(not(any(feature = "wasmi", feature = "wasmtime")))]
impl<'a, T> VmCaller<'a, T> {
pub fn none() -> Self {
VmCaller {
Expand Down
8 changes: 6 additions & 2 deletions soroban-env-host/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ exclude = ["observations/"]

[dependencies]
soroban-builtin-sdk-macros = { workspace = true }
soroban-env-common = { workspace = true, features = ["std", "wasmi", "shallow-val-hash"] }
wasmi = { workspace = true }
soroban-env-common = { workspace = true, features = ["std", "shallow-val-hash"] }
wasmi = { workspace = true, optional = true }
wasmtime = { workspace = true, optional = true }
wasmparser = { workspace = true }
stellar-strkey = "0.0.9"
static_assertions = "1.1.0"
Expand Down Expand Up @@ -90,6 +91,9 @@ default-features = false
features = ["arbitrary"]

[features]
default = ["wasmi", "wasmtime"]
wasmi = ["dep:wasmi", "soroban-env-common/wasmi"]
wasmtime = ["dep:wasmtime", "soroban-env-common/wasmtime"]
testutils = ["soroban-env-common/testutils", "recording_mode"]
backtrace = ["dep:backtrace"]
next = ["soroban-env-common/next", "stellar-xdr/next"]
Expand Down
8 changes: 8 additions & 0 deletions soroban-env-host/src/budget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@ mod dimension;
mod limits;
mod model;
mod util;
#[cfg(feature = "wasmi")]
mod wasmi_helper;
#[cfg(feature = "wasmtime")]
mod wasmtime_helper;

pub(crate) use limits::DepthLimiter;
pub use limits::{DEFAULT_HOST_DEPTH_LIMIT, DEFAULT_XDR_RW_LIMITS};
pub use model::{MeteredCostComponent, ScaledU64};

#[cfg(feature = "wasmi")]
pub(crate) use wasmi_helper::{get_wasmi_config, load_calibrated_fuel_costs};

#[cfg(feature = "wasmtime")]
pub(crate) use wasmtime_helper::get_wasmtime_config;

use std::{
cell::{RefCell, RefMut},
fmt::{Debug, Display},
Expand Down
15 changes: 15 additions & 0 deletions soroban-env-host/src/budget/wasmtime_helper.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use crate::{budget::Budget, HostError};

pub(crate) fn get_wasmtime_config(_budget: &Budget) -> Result<wasmtime::Config, HostError> {
let mut config = wasmtime::Config::new();
config
.strategy(wasmtime::Strategy::Winch)
.debug_info(false)
.generate_address_map(false)
.consume_fuel(true)
.wasm_bulk_memory(true)
.wasm_multi_value(false)
.wasm_simd(false)
.wasm_tail_call(false);
Ok(config)
}
1 change: 1 addition & 0 deletions soroban-env-host/src/cost_runner/cost_types/vm_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ mod v21 {
host.get_ledger_protocol_version()
.expect("protocol version"),
sample.module.wasmi_module.engine(),
sample.module.wasmtime_module.engine(),
&sample.wasm[..],
sample.module.cost_inputs.clone(),
)
Expand Down
17 changes: 17 additions & 0 deletions soroban-env-host/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ pub(crate) const MIN_LEDGER_PROTOCOL_VERSION: u32 = 22;
#[derive(Clone, Default)]
struct HostImpl {
module_cache: RefCell<Option<ModuleCache>>,
last_vm_fuel: RefCell<u64>,
source_account: RefCell<Option<AccountId>>,
ledger: RefCell<Option<LedgerInfo>>,
objects: RefCell<Vec<HostObject>>,
Expand Down Expand Up @@ -216,6 +217,12 @@ impl_checked_borrow_helpers!(
try_borrow_module_cache,
try_borrow_module_cache_mut
);
impl_checked_borrow_helpers!(
last_vm_fuel,
u64,
try_borrow_last_vm_fuel,
try_borrow_last_vm_fuel_mut
);
impl_checked_borrow_helpers!(
source_account,
Option<AccountId>,
Expand Down Expand Up @@ -353,6 +360,7 @@ impl Host {
let _client = tracy_client::Client::start();
Self(Rc::new(HostImpl {
module_cache: RefCell::new(None),
last_vm_fuel: RefCell::new(0),
source_account: RefCell::new(None),
ledger: RefCell::new(None),
objects: Default::default(),
Expand Down Expand Up @@ -428,6 +436,15 @@ impl Host {
})
}

pub(crate) fn get_last_vm_fuel(&self) -> Result<u64, HostError> {
Ok(*self.try_borrow_last_vm_fuel()?)
}

pub(crate) fn set_last_vm_fuel(&self, fuel: u64) -> Result<(), HostError> {
*self.try_borrow_last_vm_fuel_mut()? = fuel;
Ok(())
}

#[cfg(any(test, feature = "recording_mode"))]
pub fn in_storage_recording_mode(&self) -> Result<bool, HostError> {
if let crate::storage::FootprintMode::Recording(_) = self.try_borrow_storage()?.mode {
Expand Down
Loading

0 comments on commit 976fa5d

Please sign in to comment.