From 9b71a9f451098cb999fee8dfdc0edf957add1bab Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Wed, 19 Mar 2025 17:51:54 +0100 Subject: [PATCH 1/2] move load/store stuff into memory.rs module --- crates/core/src/lib.rs | 12 +-- crates/core/src/memory.rs | 212 ++++++++++++++++++++++++++++++++++++++ crates/core/src/value.rs | 139 ------------------------- crates/core/src/wasm.rs | 77 +------------- 4 files changed, 215 insertions(+), 225 deletions(-) create mode 100644 crates/core/src/memory.rs diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 062909b3ba..5de22f4f34 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -14,6 +14,7 @@ mod float; pub mod hint; mod host_error; +mod memory; mod trap; mod typed; mod untyped; @@ -24,16 +25,7 @@ extern crate alloc; #[cfg(feature = "std")] extern crate std; -use self::value::{ - ExtendInto, - Float, - Integer, - LittleEndianConvert, - SignExtendFrom, - TruncateSaturateInto, - TryTruncateInto, - WrapInto, -}; +use self::value::{Float, Integer, SignExtendFrom, TruncateSaturateInto, TryTruncateInto}; pub use self::{ float::{F32, F64}, host_error::HostError, diff --git a/crates/core/src/memory.rs b/crates/core/src/memory.rs new file mode 100644 index 0000000000..f99beba2ac --- /dev/null +++ b/crates/core/src/memory.rs @@ -0,0 +1,212 @@ +use crate::TrapCode; + +/// Convert one type to another by wrapping. +pub trait WrapInto { + /// Convert one type to another by wrapping. + fn wrap_into(self) -> T; +} + +macro_rules! impl_wrap_into { + ($from:ident, $into:ident) => { + impl WrapInto<$into> for $from { + #[inline] + fn wrap_into(self) -> $into { + self as $into + } + } + }; +} + +impl_wrap_into!(i32, i8); +impl_wrap_into!(i32, i16); +impl_wrap_into!(i64, i8); +impl_wrap_into!(i64, i16); +impl_wrap_into!(i64, i32); + +impl_wrap_into!(u32, u32); +impl_wrap_into!(u64, u64); + +/// Convert one type to another by extending with leading zeroes. +pub trait ExtendInto { + /// Convert one type to another by extending with leading zeroes. + fn extend_into(self) -> T; +} + +macro_rules! impl_extend_into { + ($from:ident, $into:ident) => { + impl ExtendInto<$into> for $from { + #[inline] + #[allow(clippy::cast_lossless)] + fn extend_into(self) -> $into { + self as $into + } + } + }; +} + +impl_extend_into!(i8, i32); +impl_extend_into!(u8, i32); +impl_extend_into!(i16, i32); +impl_extend_into!(u16, i32); +impl_extend_into!(i8, i64); +impl_extend_into!(u8, i64); +impl_extend_into!(i16, i64); +impl_extend_into!(u16, i64); +impl_extend_into!(i32, i64); +impl_extend_into!(u32, i64); +impl_extend_into!(u32, u64); + +// Casting to self +impl_extend_into!(u32, u32); +impl_extend_into!(u64, u64); + +/// Allows to efficiently load bytes from `memory` into a buffer. +pub trait LoadInto { + /// Loads bytes from `memory` into `self`. + /// + /// # Errors + /// + /// Traps if the `memory` access is out of bounds. + fn load_into(&mut self, memory: &[u8], address: usize) -> Result<(), TrapCode>; +} + +impl LoadInto for [u8; N] { + #[inline] + fn load_into(&mut self, memory: &[u8], address: usize) -> Result<(), TrapCode> { + let slice: &Self = memory + .get(address..) + .and_then(|slice| slice.get(..N)) + .and_then(|slice| slice.try_into().ok()) + .ok_or(TrapCode::MemoryOutOfBounds)?; + *self = *slice; + Ok(()) + } +} + +/// Allows to efficiently write bytes from a buffer into `memory`. +pub trait StoreFrom { + /// Writes bytes from `self` to `memory`. + /// + /// # Errors + /// + /// Traps if the `memory` access is out of bounds. + fn store_from(&self, memory: &mut [u8], address: usize) -> Result<(), TrapCode>; +} + +impl StoreFrom for [u8; N] { + #[inline] + fn store_from(&self, memory: &mut [u8], address: usize) -> Result<(), TrapCode> { + let slice: &mut Self = memory + .get_mut(address..) + .and_then(|slice| slice.get_mut(..N)) + .and_then(|slice| slice.try_into().ok()) + .ok_or(TrapCode::MemoryOutOfBounds)?; + *slice = *self; + Ok(()) + } +} + +/// Types that can be converted from and to little endian bytes. +pub trait LittleEndianConvert { + /// The little endian bytes representation. + type Bytes: Default + LoadInto + StoreFrom; + + /// Converts `self` into little endian bytes. + fn into_le_bytes(self) -> Self::Bytes; + + /// Converts little endian bytes into `Self`. + fn from_le_bytes(bytes: Self::Bytes) -> Self; +} + +macro_rules! impl_little_endian_convert_primitive { + ( $($primitive:ty),* $(,)? ) => { + $( + impl LittleEndianConvert for $primitive { + type Bytes = [::core::primitive::u8; ::core::mem::size_of::<$primitive>()]; + + #[inline] + fn into_le_bytes(self) -> Self::Bytes { + <$primitive>::to_le_bytes(self) + } + + #[inline] + fn from_le_bytes(bytes: Self::Bytes) -> Self { + <$primitive>::from_le_bytes(bytes) + } + } + )* + }; +} +impl_little_endian_convert_primitive!(u8, u16, u32, u64, i8, i16, i32, i64, f32, f64); + +/// Calculates the effective address of a linear memory access. +/// +/// # Errors +/// +/// If the resulting effective address overflows. +fn effective_address(ptr: u64, offset: u64) -> Result { + let Some(address) = ptr.checked_add(offset) else { + return Err(TrapCode::MemoryOutOfBounds); + }; + usize::try_from(address).map_err(|_| TrapCode::MemoryOutOfBounds) +} + +/// Executes a generic `T.loadN_[s|u]` Wasm operation. +/// +/// # Errors +/// +/// - If `ptr + offset` overflows. +/// - If `ptr + offset` loads out of bounds from `memory`. +pub fn load_extend(memory: &[u8], ptr: u64, offset: u64) -> Result +where + U: LittleEndianConvert + ExtendInto, +{ + let address = effective_address(ptr, offset)?; + load_extend_at::(memory, address) +} + +/// Executes a generic `T.loadN_[s|u]` Wasm operation. +/// +/// # Errors +/// +/// If `address` loads out of bounds from `memory`. +pub fn load_extend_at(memory: &[u8], address: usize) -> Result +where + U: LittleEndianConvert + ExtendInto, +{ + let mut buffer = <::Bytes as Default>::default(); + buffer.load_into(memory, address)?; + let value: T = ::from_le_bytes(buffer).extend_into(); + Ok(value) +} + +/// Executes a generic `T.store[N]` Wasm operation. +/// +/// # Errors +/// +/// - If `ptr + offset` overflows. +/// - If `ptr + offset` stores out of bounds from `memory`. +pub fn store_wrap(memory: &mut [u8], ptr: u64, offset: u64, value: T) -> Result<(), TrapCode> +where + T: WrapInto, + U: LittleEndianConvert, +{ + let address = effective_address(ptr, offset)?; + store_wrap_at::(memory, address, value) +} + +/// Executes a generic `T.store[N]` Wasm operation. +/// +/// # Errors +/// +/// - If `address` stores out of bounds from `memory`. +pub fn store_wrap_at(memory: &mut [u8], address: usize, value: T) -> Result<(), TrapCode> +where + T: WrapInto, + U: LittleEndianConvert, +{ + let wrapped = value.wrap_into(); + let buffer = ::into_le_bytes(wrapped); + buffer.store_from(memory, address)?; + Ok(()) +} diff --git a/crates/core/src/value.rs b/crates/core/src/value.rs index aa13d8268b..8fa50f0ee6 100644 --- a/crates/core/src/value.rs +++ b/crates/core/src/value.rs @@ -38,12 +38,6 @@ impl ValType { } } -/// Convert one type to another by wrapping. -pub trait WrapInto { - /// Convert one type to another by wrapping. - fn wrap_into(self) -> T; -} - /// Convert one type to another by rounding to the nearest integer towards zero. /// /// # Errors @@ -77,97 +71,12 @@ pub trait TruncateSaturateInto { fn truncate_saturate_into(self) -> T; } -/// Convert one type to another by extending with leading zeroes. -pub trait ExtendInto { - /// Convert one type to another by extending with leading zeroes. - fn extend_into(self) -> T; -} - /// Sign-extends `Self` integer type from `T` integer type. pub trait SignExtendFrom { /// Convert one type to another by extending with leading zeroes. fn sign_extend_from(self) -> Self; } -/// Allows to efficiently load bytes from `memory` into a buffer. -pub trait LoadInto { - /// Loads bytes from `memory` into `self`. - /// - /// # Errors - /// - /// Traps if the `memory` access is out of bounds. - fn load_into(&mut self, memory: &[u8], address: usize) -> Result<(), TrapCode>; -} - -impl LoadInto for [u8; N] { - #[inline] - fn load_into(&mut self, memory: &[u8], address: usize) -> Result<(), TrapCode> { - let slice: &Self = memory - .get(address..) - .and_then(|slice| slice.get(..N)) - .and_then(|slice| slice.try_into().ok()) - .ok_or(TrapCode::MemoryOutOfBounds)?; - *self = *slice; - Ok(()) - } -} - -/// Allows to efficiently write bytes from a buffer into `memory`. -pub trait StoreFrom { - /// Writes bytes from `self` to `memory`. - /// - /// # Errors - /// - /// Traps if the `memory` access is out of bounds. - fn store_from(&self, memory: &mut [u8], address: usize) -> Result<(), TrapCode>; -} - -impl StoreFrom for [u8; N] { - #[inline] - fn store_from(&self, memory: &mut [u8], address: usize) -> Result<(), TrapCode> { - let slice: &mut Self = memory - .get_mut(address..) - .and_then(|slice| slice.get_mut(..N)) - .and_then(|slice| slice.try_into().ok()) - .ok_or(TrapCode::MemoryOutOfBounds)?; - *slice = *self; - Ok(()) - } -} - -/// Types that can be converted from and to little endian bytes. -pub trait LittleEndianConvert { - /// The little endian bytes representation. - type Bytes: Default + LoadInto + StoreFrom; - - /// Converts `self` into little endian bytes. - fn into_le_bytes(self) -> Self::Bytes; - - /// Converts little endian bytes into `Self`. - fn from_le_bytes(bytes: Self::Bytes) -> Self; -} - -macro_rules! impl_little_endian_convert_primitive { - ( $($primitive:ty),* $(,)? ) => { - $( - impl LittleEndianConvert for $primitive { - type Bytes = [::core::primitive::u8; ::core::mem::size_of::<$primitive>()]; - - #[inline] - fn into_le_bytes(self) -> Self::Bytes { - <$primitive>::to_le_bytes(self) - } - - #[inline] - fn from_le_bytes(bytes: Self::Bytes) -> Self { - <$primitive>::from_le_bytes(bytes) - } - } - )* - }; -} -impl_little_endian_convert_primitive!(u8, u16, u32, u64, i8, i16, i32, i64, f32, f64); - /// Integer value. pub trait Integer: Sized + Unsigned { /// Returns `true` if `self` is zero. @@ -262,26 +171,6 @@ pub trait Float: Sized { fn copysign(lhs: Self, rhs: Self) -> Self; } -macro_rules! impl_wrap_into { - ($from:ident, $into:ident) => { - impl WrapInto<$into> for $from { - #[inline] - fn wrap_into(self) -> $into { - self as $into - } - } - }; -} - -impl_wrap_into!(i32, i8); -impl_wrap_into!(i32, i16); -impl_wrap_into!(i64, i8); -impl_wrap_into!(i64, i16); -impl_wrap_into!(i64, i32); - -impl_wrap_into!(u32, u32); -impl_wrap_into!(u64, u64); - macro_rules! impl_try_truncate_into { (@primitive $from: ident, $into: ident, $rmin:literal, $rmax:literal) => { impl TryTruncateInto<$into, TrapCode> for $from { @@ -324,34 +213,6 @@ impl_try_truncate_into!(@primitive f32, u64, -1.0_f32, 1844674 impl_try_truncate_into!(@primitive f64, i64, -9223372036854777856.0_f64, 9223372036854775808.0_f64); impl_try_truncate_into!(@primitive f64, u64, -1.0_f64, 18446744073709551616.0_f64); -macro_rules! impl_extend_into { - ($from:ident, $into:ident) => { - impl ExtendInto<$into> for $from { - #[inline] - #[allow(clippy::cast_lossless)] - fn extend_into(self) -> $into { - self as $into - } - } - }; -} - -impl_extend_into!(i8, i32); -impl_extend_into!(u8, i32); -impl_extend_into!(i16, i32); -impl_extend_into!(u16, i32); -impl_extend_into!(i8, i64); -impl_extend_into!(u8, i64); -impl_extend_into!(i16, i64); -impl_extend_into!(u16, i64); -impl_extend_into!(i32, i64); -impl_extend_into!(u32, i64); -impl_extend_into!(u32, u64); - -// Casting to self -impl_extend_into!(u32, u32); -impl_extend_into!(u64, u64); - macro_rules! impl_sign_extend_from { ( $( impl SignExtendFrom<$from_type:ty> for $for_type:ty; )* ) => { $( diff --git a/crates/core/src/wasm.rs b/crates/core/src/wasm.rs index 6557a9d9bb..ebc0fddeb3 100644 --- a/crates/core/src/wasm.rs +++ b/crates/core/src/wasm.rs @@ -1,16 +1,13 @@ //! Execution helpers for Wasm or Wasmi instructions. use crate::{ - value::{LoadInto, StoreFrom}, - ExtendInto, + memory::{load_extend, load_extend_at, store_wrap, store_wrap_at}, Float, Integer, - LittleEndianConvert, SignExtendFrom, TrapCode, TruncateSaturateInto, TryTruncateInto, - WrapInto, }; use core::ops::Neg; @@ -286,47 +283,6 @@ impl_untyped_val! { fn i64_trunc_sat_f64_u(value: f64) -> u64 = TruncateSaturateInto::truncate_saturate_into; } -/// Calculates the effective address of a linear memory access. -/// -/// # Errors -/// -/// If the resulting effective address overflows. -fn effective_address(ptr: u64, offset: u64) -> Result { - let Some(address) = ptr.checked_add(offset) else { - return Err(TrapCode::MemoryOutOfBounds); - }; - usize::try_from(address).map_err(|_| TrapCode::MemoryOutOfBounds) -} - -/// Executes a generic `T.loadN_[s|u]` Wasm operation. -/// -/// # Errors -/// -/// - If `ptr + offset` overflows. -/// - If `ptr + offset` loads out of bounds from `memory`. -fn load_extend(memory: &[u8], ptr: u64, offset: u64) -> Result -where - U: LittleEndianConvert + ExtendInto, -{ - let address = effective_address(ptr, offset)?; - load_extend_at::(memory, address) -} - -/// Executes a generic `T.loadN_[s|u]` Wasm operation. -/// -/// # Errors -/// -/// If `address` loads out of bounds from `memory`. -fn load_extend_at(memory: &[u8], address: usize) -> Result -where - U: LittleEndianConvert + ExtendInto, -{ - let mut buffer = <::Bytes as Default>::default(); - buffer.load_into(memory, address)?; - let value: T = ::from_le_bytes(buffer).extend_into(); - Ok(value) -} - macro_rules! gen_load_fn { ( (fn $load_fn:ident, fn $load_at_fn:ident, $ty:ty); $($rest:tt)* @@ -378,37 +334,6 @@ gen_load_fn! { (fn i64_load32_u, fn i64_load32_u_at, u32 => i64); } -/// Executes a generic `T.store[N]` Wasm operation. -/// -/// # Errors -/// -/// - If `ptr + offset` overflows. -/// - If `ptr + offset` stores out of bounds from `memory`. -fn store_wrap(memory: &mut [u8], ptr: u64, offset: u64, value: T) -> Result<(), TrapCode> -where - T: WrapInto, - U: LittleEndianConvert, -{ - let address = effective_address(ptr, offset)?; - store_wrap_at::(memory, address, value) -} - -/// Executes a generic `T.store[N]` Wasm operation. -/// -/// # Errors -/// -/// - If `address` stores out of bounds from `memory`. -fn store_wrap_at(memory: &mut [u8], address: usize, value: T) -> Result<(), TrapCode> -where - T: WrapInto, - U: LittleEndianConvert, -{ - let wrapped = value.wrap_into(); - let buffer = ::into_le_bytes(wrapped); - buffer.store_from(memory, address)?; - Ok(()) -} - macro_rules! gen_store_fn { ( (fn $store_fn:ident, fn $store_at_fn:ident, $ty:ty); $($rest:tt)* From 95be6ba63388667b05efb668e5fb5a0abc1dfb52 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Wed, 19 Mar 2025 17:56:18 +0100 Subject: [PATCH 2/2] redesign WrapInto and ExtendInto macros --- crates/core/src/memory.rs | 83 +++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 38 deletions(-) diff --git a/crates/core/src/memory.rs b/crates/core/src/memory.rs index f99beba2ac..a80fd3a35f 100644 --- a/crates/core/src/memory.rs +++ b/crates/core/src/memory.rs @@ -7,24 +7,28 @@ pub trait WrapInto { } macro_rules! impl_wrap_into { - ($from:ident, $into:ident) => { - impl WrapInto<$into> for $from { - #[inline] - fn wrap_into(self) -> $into { - self as $into + ( + $( impl WrapInto<$into:ident> for $from:ident; )* + ) => { + $( + impl WrapInto<$into> for $from { + #[inline] + fn wrap_into(self) -> $into { + self as $into + } } - } + )* }; } - -impl_wrap_into!(i32, i8); -impl_wrap_into!(i32, i16); -impl_wrap_into!(i64, i8); -impl_wrap_into!(i64, i16); -impl_wrap_into!(i64, i32); - -impl_wrap_into!(u32, u32); -impl_wrap_into!(u64, u64); +impl_wrap_into! { + impl WrapInto for i32; + impl WrapInto for i32; + impl WrapInto for i64; + impl WrapInto for i64; + impl WrapInto for i64; + impl WrapInto for u32; + impl WrapInto for u64; +} /// Convert one type to another by extending with leading zeroes. pub trait ExtendInto { @@ -33,32 +37,35 @@ pub trait ExtendInto { } macro_rules! impl_extend_into { - ($from:ident, $into:ident) => { - impl ExtendInto<$into> for $from { - #[inline] - #[allow(clippy::cast_lossless)] - fn extend_into(self) -> $into { - self as $into + ( + $( impl ExtendInto<$into:ident> for $from:ident; )* + ) => { + $( + impl ExtendInto<$into> for $from { + #[inline] + #[allow(clippy::cast_lossless)] + fn extend_into(self) -> $into { + self as $into + } } - } + )* }; } - -impl_extend_into!(i8, i32); -impl_extend_into!(u8, i32); -impl_extend_into!(i16, i32); -impl_extend_into!(u16, i32); -impl_extend_into!(i8, i64); -impl_extend_into!(u8, i64); -impl_extend_into!(i16, i64); -impl_extend_into!(u16, i64); -impl_extend_into!(i32, i64); -impl_extend_into!(u32, i64); -impl_extend_into!(u32, u64); - -// Casting to self -impl_extend_into!(u32, u32); -impl_extend_into!(u64, u64); +impl_extend_into! { + impl ExtendInto for i8; + impl ExtendInto for u8; + impl ExtendInto for i16; + impl ExtendInto for u16; + impl ExtendInto for i8; + impl ExtendInto for u8; + impl ExtendInto for i16; + impl ExtendInto for u16; + impl ExtendInto for i32; + impl ExtendInto for u32; + impl ExtendInto for u32; + impl ExtendInto for u32; + impl ExtendInto for u64; +} /// Allows to efficiently load bytes from `memory` into a buffer. pub trait LoadInto {