From 4432eb3e92e0678120e6eef9edaf0ee003fe326f Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Sat, 2 Dec 2023 21:56:08 +0100 Subject: [PATCH 1/7] Fix bug in fuel costs metering for `RegisterList` based instructions (#823) fix bug in fuel costs metering for register list instructions --- crates/wasmi/src/engine/translator/instr_encoder.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/wasmi/src/engine/translator/instr_encoder.rs b/crates/wasmi/src/engine/translator/instr_encoder.rs index e1e08307e2..761474a9b1 100644 --- a/crates/wasmi/src/engine/translator/instr_encoder.rs +++ b/crates/wasmi/src/engine/translator/instr_encoder.rs @@ -417,6 +417,10 @@ impl InstrEncoder { } [v0, v1, rest @ ..] => { debug_assert!(!rest.is_empty()); + // Note: The fuel for copies might result in 0 charges if there aren't + // enough copies to account for at least 1 fuel. Therefore we need + // to also bump by `FuelCosts::base` to charge at least 1 fuel. + self.bump_fuel_consumption(fuel_info, FuelCosts::base)?; self.bump_fuel_consumption(fuel_info, |costs| { costs.fuel_for_copies(rest.len() as u64 + 3) })?; @@ -567,6 +571,10 @@ impl InstrEncoder { } [v0, v1, v2, rest @ ..] => { debug_assert!(!rest.is_empty()); + // Note: The fuel for return values might result in 0 charges if there aren't + // enough return values to account for at least 1 fuel. Therefore we need + // to also bump by `FuelCosts::base` to charge at least 1 fuel. + self.bump_fuel_consumption(fuel_info, FuelCosts::base)?; self.bump_fuel_consumption(fuel_info, |costs| { costs.fuel_for_copies(rest.len() as u64 + 3) })?; @@ -625,6 +633,10 @@ impl InstrEncoder { } [v0, v1, rest @ ..] => { debug_assert!(!rest.is_empty()); + // Note: The fuel for return values might result in 0 charges if there aren't + // enough return values to account for at least 1 fuel. Therefore we need + // to also bump by `FuelCosts::base` to charge at least 1 fuel. + self.bump_fuel_consumption(fuel_info, FuelCosts::base)?; self.bump_fuel_consumption(fuel_info, |costs| { costs.fuel_for_copies(rest.len() as u64 + 3) })?; From b02e23d478f79158c07c6b068bf91f388185e0a9 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Sat, 2 Dec 2023 21:56:18 +0100 Subject: [PATCH 2/7] Fix bug in new register-machine executor (#824) fix bug in executor --- crates/wasmi/src/engine/executor/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/wasmi/src/engine/executor/mod.rs b/crates/wasmi/src/engine/executor/mod.rs index 2069cc95a9..ad6fa67abf 100644 --- a/crates/wasmi/src/engine/executor/mod.rs +++ b/crates/wasmi/src/engine/executor/mod.rs @@ -198,7 +198,9 @@ impl<'engine> EngineExecutor<'engine> { match ctx.as_context().store.inner.resolve_func(func) { FuncEntity::Wasm(wasm_func) => { // We reserve space on the stack to write the results of the root function execution. - self.stack.values.extend_zeros(results.len_results()); + let len_results = results.len_results(); + self.stack.values.reserve(len_results)?; + self.stack.values.extend_zeros(len_results); let instance = wasm_func.instance(); let compiled_func = self.res.code_map.get(wasm_func.func_body()); let (base_ptr, frame_ptr) = self.stack.values.alloc_call_frame(compiled_func)?; From ed8ce84baca52913b295deef5617fc813fd0940a Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Sun, 3 Dec 2023 13:25:04 +0100 Subject: [PATCH 3/7] Refactor bytecode const utilities (#826) * refactor AnyConst{16,32}, Const16 and Const32 APIs They now offer all their API via From and TryFrom impls if possible. * apply rustfmt * generalize Const16::is_zero method * fix SAFETY comment --- crates/wasmi/src/engine/bytecode/immediate.rs | 476 ++++++++++-------- crates/wasmi/src/engine/executor/instrs.rs | 2 +- .../wasmi/src/engine/executor/instrs/copy.rs | 4 +- .../src/engine/executor/instrs/return_.rs | 2 +- .../src/engine/executor/instrs/select.rs | 4 +- .../src/engine/translator/instr_encoder.rs | 12 +- crates/wasmi/src/engine/translator/mod.rs | 2 +- .../translator/tests/op/binary/i32_add.rs | 2 +- .../translator/tests/op/binary/i32_and.rs | 6 +- .../translator/tests/op/binary/i32_div_s.rs | 2 +- .../translator/tests/op/binary/i32_div_u.rs | 2 +- .../translator/tests/op/binary/i32_mul.rs | 6 +- .../translator/tests/op/binary/i32_or.rs | 6 +- .../translator/tests/op/binary/i32_rem_s.rs | 8 +- .../translator/tests/op/binary/i32_rem_u.rs | 4 +- .../translator/tests/op/binary/i32_rotl.rs | 6 +- .../translator/tests/op/binary/i32_rotr.rs | 6 +- .../translator/tests/op/binary/i32_shl.rs | 4 +- .../translator/tests/op/binary/i32_shr_s.rs | 6 +- .../translator/tests/op/binary/i32_shr_u.rs | 4 +- .../translator/tests/op/binary/i32_sub.rs | 4 +- .../translator/tests/op/binary/i32_xor.rs | 4 +- .../engine/translator/tests/op/binary/mod.rs | 2 +- .../src/engine/translator/tests/op/block.rs | 4 +- .../engine/translator/tests/op/cmp/f64_eq.rs | 4 +- .../engine/translator/tests/op/cmp/i32_ne.rs | 6 +- .../engine/translator/tests/op/cmp/i64_ne.rs | 6 +- .../engine/translator/tests/op/global_set.rs | 8 +- .../src/engine/translator/tests/op/mod.rs | 16 +- .../src/engine/translator/tests/wasm_type.rs | 6 +- crates/wasmi/src/engine/translator/utils.rs | 2 +- crates/wasmi/src/engine/translator/visit.rs | 8 +- 32 files changed, 333 insertions(+), 301 deletions(-) diff --git a/crates/wasmi/src/engine/bytecode/immediate.rs b/crates/wasmi/src/engine/bytecode/immediate.rs index 06dbf40c08..914aa2c931 100644 --- a/crates/wasmi/src/engine/bytecode/immediate.rs +++ b/crates/wasmi/src/engine/bytecode/immediate.rs @@ -1,4 +1,8 @@ -use core::{fmt::Debug, marker::PhantomData}; +use core::{ + fmt::Debug, + marker::PhantomData, + num::{NonZeroI16, NonZeroI32, NonZeroI64, NonZeroU16, NonZeroU32, NonZeroU64}, +}; use wasmi_core::{F32, F64}; /// Error that may occur upon converting values to [`Const16`]. @@ -14,15 +18,8 @@ pub struct Const16 { marker: PhantomData T>, } -impl Const16 { - /// Returns `true` if the [`Const16`]`` is equal to zero. - pub fn is_zero(&self) -> bool { - self.inner == AnyConst16::from(0_i16) - } -} - -impl Const16 { - /// Returns `true` if the [`Const16`]`` is equal to zero. +impl Const16 { + /// Returns `true` if the [`Const16`] is equal to zero. pub fn is_zero(&self) -> bool { self.inner == AnyConst16::from(0_i16) } @@ -30,7 +27,7 @@ impl Const16 { impl Const16 { /// Crete a new typed [`Const16`] value. - pub fn new(inner: AnyConst16) -> Self { + fn new(inner: AnyConst16) -> Self { Self { inner, marker: PhantomData, @@ -56,49 +53,105 @@ impl Eq for Const16 {} impl From for Const16 { fn from(value: i16) -> Self { - Self::new(AnyConst16::from_i16(value)) + Self::new(AnyConst16::from(value)) } } impl From for Const16 { fn from(value: u16) -> Self { - Self::new(AnyConst16::from_u16(value)) + Self::new(AnyConst16::from(value)) } } impl From for Const16 { fn from(value: i16) -> Self { - Self::new(AnyConst16::from_i16(value)) + Self::new(AnyConst16::from(value)) } } impl From for Const16 { fn from(value: u16) -> Self { - Self::new(AnyConst16::from_u16(value)) + Self::new(AnyConst16::from(value)) + } +} + +impl From for Const16 { + fn from(value: NonZeroI16) -> Self { + Self::new(AnyConst16::from(value.get())) + } +} + +impl From for Const16 { + fn from(value: NonZeroU16) -> Self { + Self::new(AnyConst16::from(value.get())) + } +} + +impl From for Const16 { + fn from(value: NonZeroI16) -> Self { + Self::new(AnyConst16::from(value.get())) + } +} + +impl From for Const16 { + fn from(value: NonZeroU16) -> Self { + Self::new(AnyConst16::from(value.get())) } } impl From> for i32 { - fn from(value: Const16) -> Self { - value.inner.to_i32() + fn from(value: Const16) -> Self { + Self::from(value.inner) } } impl From> for u32 { - fn from(value: Const16) -> Self { - value.inner.to_u32() + fn from(value: Const16) -> Self { + Self::from(value.inner) } } impl From> for i64 { - fn from(value: Const16) -> Self { - value.inner.to_i64() + fn from(value: Const16) -> Self { + Self::from(value.inner) } } impl From> for u64 { - fn from(value: Const16) -> Self { - value.inner.to_u64() + fn from(value: Const16) -> Self { + Self::from(value.inner) + } +} + +impl From> for NonZeroI32 { + fn from(value: Const16) -> Self { + // SAFETY: Due to construction of `Const16` we are guaranteed + // that `value.inner` is a valid non-zero value. + unsafe { Self::new_unchecked(i32::from(value.inner)) } + } +} + +impl From> for NonZeroU32 { + fn from(value: Const16) -> Self { + // SAFETY: Due to construction of `Const16` we are guaranteed + // that `value.inner` is a valid non-zero value. + unsafe { Self::new_unchecked(u32::from(value.inner)) } + } +} + +impl From> for NonZeroI64 { + fn from(value: Const16) -> Self { + // SAFETY: Due to construction of `Const16` we are guaranteed + // that `value.inner` is a valid non-zero value. + unsafe { Self::new_unchecked(i64::from(value.inner)) } + } +} + +impl From> for NonZeroU64 { + fn from(value: Const16) -> Self { + // SAFETY: Due to construction of `Const16` we are guaranteed + // that `value.inner` is a valid non-zero value. + unsafe { Self::new_unchecked(u64::from(value.inner)) } } } @@ -106,55 +159,63 @@ impl TryFrom for Const16 { type Error = OutOfBoundsConst; fn try_from(value: i32) -> Result { - Self::from_i32(value).ok_or(OutOfBoundsConst) + AnyConst16::try_from(value).map(Self::new) } } -impl TryFrom for Const16 { +impl TryFrom for Const16 { type Error = OutOfBoundsConst; - fn try_from(value: u32) -> Result { - Self::from_u32(value).ok_or(OutOfBoundsConst) + fn try_from(value: NonZeroI32) -> Result { + AnyConst16::try_from(value).map(Self::new) } } -impl TryFrom for Const16 { +impl TryFrom for Const16 { type Error = OutOfBoundsConst; - fn try_from(value: i64) -> Result { - Self::from_i64(value).ok_or(OutOfBoundsConst) + fn try_from(value: u32) -> Result { + AnyConst16::try_from(value).map(Self::new) } } -impl TryFrom for Const16 { +impl TryFrom for Const16 { type Error = OutOfBoundsConst; - fn try_from(value: u64) -> Result { - Self::from_u64(value).ok_or(OutOfBoundsConst) + fn try_from(value: NonZeroU32) -> Result { + AnyConst16::try_from(value).map(Self::new) } } -impl Const16 { - pub fn from_i32(value: i32) -> Option { - i16::try_from(value).map(Self::from).ok() +impl TryFrom for Const16 { + type Error = OutOfBoundsConst; + + fn try_from(value: i64) -> Result { + AnyConst16::try_from(value).map(Self::new) } } -impl Const16 { - pub fn from_u32(value: u32) -> Option { - u16::try_from(value).map(Self::from).ok() +impl TryFrom for Const16 { + type Error = OutOfBoundsConst; + + fn try_from(value: NonZeroI64) -> Result { + AnyConst16::try_from(value).map(Self::new) } } -impl Const16 { - pub fn from_i64(value: i64) -> Option { - i16::try_from(value).map(Self::from).ok() +impl TryFrom for Const16 { + type Error = OutOfBoundsConst; + + fn try_from(value: u64) -> Result { + AnyConst16::try_from(value).map(Self::new) } } -impl Const16 { - pub fn from_u64(value: u64) -> Option { - u16::try_from(value).map(Self::from).ok() +impl TryFrom for Const16 { + type Error = OutOfBoundsConst; + + fn try_from(value: NonZeroU64) -> Result { + AnyConst16::try_from(value).map(Self::new) } } @@ -179,7 +240,7 @@ where impl Const32 { /// Crete a new typed [`Const32`] value. - pub fn new(inner: AnyConst32) -> Self { + fn new(inner: AnyConst32) -> Self { Self { inner, marker: PhantomData, @@ -205,67 +266,67 @@ impl Eq for Const32 {} impl From for Const32 { fn from(value: i32) -> Self { - Self::new(AnyConst32::from_i32(value)) + Self::new(AnyConst32::from(value)) } } impl From for Const32 { fn from(value: u32) -> Self { - Self::new(AnyConst32::from_u32(value)) + Self::new(AnyConst32::from(value)) } } impl From for Const32 { fn from(value: i32) -> Self { - Self::new(AnyConst32::from_i32(value)) + Self::new(AnyConst32::from(value)) } } impl From for Const32 { fn from(value: u32) -> Self { - Self::new(AnyConst32::from_u32(value)) + Self::new(AnyConst32::from(value)) } } impl From for Const32 { fn from(value: f32) -> Self { - Self::new(AnyConst32::from_f32(F32::from(value))) + Self::new(AnyConst32::from(value)) } } impl From> for i32 { - fn from(value: Const32) -> Self { - value.inner.to_i32() + fn from(value: Const32) -> Self { + Self::from(value.inner) } } impl From> for u32 { - fn from(value: Const32) -> Self { - value.inner.to_u32() + fn from(value: Const32) -> Self { + Self::from(value.inner) } } impl From> for i64 { - fn from(value: Const32) -> Self { - value.inner.to_i64() + fn from(value: Const32) -> Self { + Self::from(value.inner) } } impl From> for u64 { - fn from(value: Const32) -> Self { - value.inner.to_u64() + fn from(value: Const32) -> Self { + Self::from(value.inner) } } impl From> for f32 { - fn from(value: Const32) -> Self { - f32::from(value.inner.to_f32()) + fn from(value: Const32) -> Self { + Self::from(value.inner) } } impl From> for f64 { - fn from(value: Const32) -> Self { - f64::from(value.inner.to_f64()) + fn from(value: Const32) -> Self { + Self::from(value.inner) } } @@ -273,7 +334,7 @@ impl TryFrom for Const32 { type Error = OutOfBoundsConst; fn try_from(value: i64) -> Result { - Self::from_i64(value).ok_or(OutOfBoundsConst) + AnyConst32::try_from(value).map(Self::new) } } @@ -281,7 +342,7 @@ impl TryFrom for Const32 { type Error = OutOfBoundsConst; fn try_from(value: u64) -> Result { - Self::from_u64(value).ok_or(OutOfBoundsConst) + AnyConst32::try_from(value).map(Self::new) } } @@ -289,28 +350,7 @@ impl TryFrom for Const32 { type Error = OutOfBoundsConst; fn try_from(value: f64) -> Result { - Self::from_f64(value).ok_or(OutOfBoundsConst) - } -} - -impl Const32 { - /// Creates a new [`Const32`] from the given `i64` value if possible. - pub fn from_i64(value: i64) -> Option { - i32::try_from(value).map(Self::from).ok() - } -} - -impl Const32 { - /// Creates a new [`Const32`] from the given `u64` value if possible. - pub fn from_u64(value: u64) -> Option { - u32::try_from(value).map(Self::from).ok() - } -} - -impl Const32 { - /// Creates a new [`Const32`] from the given `f64` value if possible. - pub fn from_f64(value: f64) -> Option { - AnyConst32::from_f64(value).map(Self::new) + AnyConst32::try_from(value).map(Self::new) } } @@ -328,7 +368,9 @@ impl TryFrom for AnyConst16 { type Error = OutOfBoundsConst; fn try_from(value: i32) -> Result { - Self::from_i32(value).ok_or(OutOfBoundsConst) + i16::try_from(value) + .map(Self::from) + .map_err(|_| OutOfBoundsConst) } } @@ -336,7 +378,9 @@ impl TryFrom for AnyConst16 { type Error = OutOfBoundsConst; fn try_from(value: u32) -> Result { - Self::from_u32(value).ok_or(OutOfBoundsConst) + u16::try_from(value) + .map(Self::from) + .map_err(|_| OutOfBoundsConst) } } @@ -344,7 +388,9 @@ impl TryFrom for AnyConst16 { type Error = OutOfBoundsConst; fn try_from(value: i64) -> Result { - Self::from_i64(value).ok_or(OutOfBoundsConst) + i16::try_from(value) + .map(Self::from) + .map_err(|_| OutOfBoundsConst) } } @@ -352,73 +398,89 @@ impl TryFrom for AnyConst16 { type Error = OutOfBoundsConst; fn try_from(value: u64) -> Result { - Self::from_u64(value).ok_or(OutOfBoundsConst) + u16::try_from(value) + .map(Self::from) + .map_err(|_| OutOfBoundsConst) } } -impl From for AnyConst16 { - fn from(value: i16) -> Self { - Self::from_i16(value) +impl TryFrom for AnyConst16 { + type Error = OutOfBoundsConst; + + fn try_from(value: NonZeroI32) -> Result { + NonZeroI16::try_from(value) + .map(NonZeroI16::get) + .map(Self::from) + .map_err(|_| OutOfBoundsConst) } } -impl From for AnyConst16 { - fn from(value: u16) -> Self { - Self::from_u16(value) +impl TryFrom for AnyConst16 { + type Error = OutOfBoundsConst; + + fn try_from(value: NonZeroU32) -> Result { + NonZeroU16::try_from(value) + .map(NonZeroU16::get) + .map(Self::from) + .map_err(|_| OutOfBoundsConst) } } -impl AnyConst16 { - /// Creates an [`Const16`] from the given `i16` value. - pub fn from_i16(value: i16) -> Self { - Self(value) - } +impl TryFrom for AnyConst16 { + type Error = OutOfBoundsConst; - /// Creates an [`Const16`] from the given `u16` value. - pub fn from_u16(value: u16) -> Self { - Self::from_i16(value as i16) + fn try_from(value: NonZeroI64) -> Result { + NonZeroI16::try_from(value) + .map(NonZeroI16::get) + .map(Self::from) + .map_err(|_| OutOfBoundsConst) } +} - /// Creates an [`Const16`] from the given `i32` value if possible. - pub fn from_i32(value: i32) -> Option { - i16::try_from(value).ok().map(Self) - } +impl TryFrom for AnyConst16 { + type Error = OutOfBoundsConst; - /// Creates an [`Const16`] from the given `u32` value if possible. - pub fn from_u32(value: u32) -> Option { - let value = u16::try_from(value).ok()? as i16; - Some(Self(value)) + fn try_from(value: NonZeroU64) -> Result { + NonZeroU16::try_from(value) + .map(NonZeroU16::get) + .map(Self::from) + .map_err(|_| OutOfBoundsConst) } +} - /// Creates an [`Const16`] from the given `i64` value if possible. - pub fn from_i64(value: i64) -> Option { - i16::try_from(value).ok().map(Self) +impl From for AnyConst16 { + fn from(value: i16) -> Self { + Self(value) } +} - /// Creates an [`Const16`] from the given `u64` value if possible. - pub fn from_u64(value: u64) -> Option { - let value = u16::try_from(value).ok()? as i16; - Some(Self(value)) +impl From for AnyConst16 { + fn from(value: u16) -> Self { + Self::from(value as i16) } +} - /// Returns an `i32` value from `self`. - pub fn to_i32(self) -> i32 { - i32::from(self.0) +impl From for i32 { + fn from(value: AnyConst16) -> Self { + Self::from(value.0) } +} - /// Returns an `i64` value from `self`. - pub fn to_i64(self) -> i64 { - i64::from(self.0) +impl From for i64 { + fn from(value: AnyConst16) -> Self { + Self::from(value.0) } +} - /// Returns an `u32` value from `self`. - pub fn to_u32(self) -> u32 { - u32::from(self.0 as u16) +impl From for u32 { + fn from(value: AnyConst16) -> Self { + Self::from(value.0 as u16) } +} - /// Returns an `u64` value from `self`. - pub fn to_u64(self) -> u64 { - u64::from(self.0 as u16) +impl From for u64 { + fn from(value: AnyConst16) -> Self { + Self::from(value.0 as u16) } } @@ -433,11 +495,23 @@ impl AnyConst16 { #[repr(align(2))] // 2-byte alignment is sufficient for `wasmi` bytecode pub struct AnyConst32([u8; 4]); +impl TryFrom for AnyConst32 { + type Error = OutOfBoundsConst; + + fn try_from(value: u64) -> Result { + u32::try_from(value) + .map(Self::from) + .map_err(|_| OutOfBoundsConst) + } +} + impl TryFrom for AnyConst32 { type Error = OutOfBoundsConst; fn try_from(value: i64) -> Result { - Self::from_i64(value).ok_or(OutOfBoundsConst) + i32::try_from(value) + .map(Self::from) + .map_err(|_| OutOfBoundsConst) } } @@ -445,37 +519,41 @@ impl TryFrom for AnyConst32 { type Error = OutOfBoundsConst; fn try_from(value: f64) -> Result { - Self::from_f64(value).ok_or(OutOfBoundsConst) + let truncated = value as f32; + if value.to_bits() != f64::from(truncated).to_bits() { + return Err(OutOfBoundsConst); + } + Ok(Self::from(truncated)) } } impl From for AnyConst32 { fn from(value: bool) -> Self { - Self::from_bool(value) + Self::from(u32::from(value)) } } impl From for AnyConst32 { fn from(value: i8) -> Self { - Self::from_u32(value as u32) + Self::from(value as u32) } } impl From for AnyConst32 { fn from(value: i16) -> Self { - Self::from_u32(value as u32) + Self::from(value as u32) } } impl From for AnyConst32 { fn from(value: i32) -> Self { - Self::from_i32(value) + Self::from(value as u32) } } impl From for AnyConst32 { fn from(value: u32) -> Self { - Self::from_u32(value) + Self(value.to_ne_bytes()) } } @@ -487,102 +565,54 @@ impl From for AnyConst32 { impl From for AnyConst32 { fn from(value: F32) -> Self { - Self::from_f32(value) + Self::from(value.to_bits()) } } -impl AnyConst32 { - /// Creates a [`AnyConst32`] from the given `bool` value. - pub fn from_bool(value: bool) -> Self { - Self::from_u32(u32::from(value)) +impl From for i32 { + fn from(value: AnyConst32) -> Self { + Self::from_ne_bytes(value.0) } +} - /// Creates an [`AnyConst32`] from the given `u32` value. - pub fn from_u32(value: u32) -> Self { - Self(value.to_ne_bytes()) +impl From for u32 { + fn from(value: AnyConst32) -> Self { + Self::from_ne_bytes(value.0) } +} - /// Creates an [`AnyConst32`] from the given `i32` value. - pub fn from_i32(value: i32) -> Self { - Self::from_u32(value as u32) +impl From for i64 { + fn from(value: AnyConst32) -> Self { + Self::from(i32::from(value)) } +} - /// Creates an [`AnyConst32`] from the given `i64` value if possible. - pub fn from_i64(value: i64) -> Option { - i32::try_from(value).ok().map(Self::from_i32) +impl From for u64 { + fn from(value: AnyConst32) -> Self { + Self::from(u32::from(value)) } +} - /// Creates an [`AnyConst32`] from the given [`F32`] value. - pub fn from_f32(value: F32) -> Self { - Self::from_u32(value.to_bits()) +impl From for f32 { + fn from(value: AnyConst32) -> Self { + f32::from_bits(u32::from(value)) } +} - /// Creates an [`AnyConst32`] from the given `f64` value if possible. - pub fn from_f64(value: f64) -> Option { - let truncated = value as f32; - if value.to_bits() != f64::from(truncated).to_bits() { - return None; - } - Some(Self::from(truncated)) - } - - /// Returns an `u32` value from `self`. - /// - /// # Note - /// - /// It is the responsibility of the user to validate type safety - /// since access via this method is not type checked. - pub fn to_u32(self) -> u32 { - u32::from_ne_bytes(self.0) - } - - /// Returns an `i32` value from `self`. - /// - /// # Note - /// - /// It is the responsibility of the user to validate type safety - /// since access via this method is not type checked. - pub fn to_i32(self) -> i32 { - self.to_u32() as i32 - } - - /// Returns an `f32` value from `self`. - /// - /// # Note - /// - /// It is the responsibility of the user to validate type safety - /// since access via this method is not type checked. - pub fn to_f32(self) -> F32 { - F32::from(f32::from_bits(self.to_u32())) - } - - /// Returns an `f32` value from `self`. - /// - /// # Note - /// - /// It is the responsibility of the user to validate type safety - /// since access via this method is not type checked. - pub fn to_f64(self) -> F64 { - F64::from(f64::from(f32::from_bits(self.to_u32()))) - } - - /// Returns an `i64` value from `self`. - /// - /// # Note - /// - /// It is the responsibility of the user to validate type safety - /// since access via this method is not type checked. - pub fn to_i64(self) -> i64 { - i64::from(self.to_i32()) - } - - /// Returns an `u64` value from `self`. - /// - /// # Note - /// - /// It is the responsibility of the user to validate type safety - /// since access via this method is not type checked. - pub fn to_u64(self) -> u64 { - u64::from(self.to_u32()) +impl From for F32 { + fn from(value: AnyConst32) -> Self { + F32::from(f32::from(value)) + } +} + +impl From for f64 { + fn from(value: AnyConst32) -> Self { + f64::from(f32::from_bits(u32::from(value))) + } +} + +impl From for F64 { + fn from(value: AnyConst32) -> Self { + F64::from(f64::from(value)) } } diff --git a/crates/wasmi/src/engine/executor/instrs.rs b/crates/wasmi/src/engine/executor/instrs.rs index 4a3d6c2f4b..245df45f6e 100644 --- a/crates/wasmi/src/engine/executor/instrs.rs +++ b/crates/wasmi/src/engine/executor/instrs.rs @@ -1100,7 +1100,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> { /// Returns the [`Instruction::Const32`] parameter for an [`Instruction`]. fn fetch_address_offset(&self, offset: usize) -> u32 { - self.fetch_const32(offset).to_u32() + u32::from(self.fetch_const32(offset)) } /// Executes a generic unary [`Instruction`]. diff --git a/crates/wasmi/src/engine/executor/instrs/copy.rs b/crates/wasmi/src/engine/executor/instrs/copy.rs index ab1e5ed258..8cb150dc04 100644 --- a/crates/wasmi/src/engine/executor/instrs/copy.rs +++ b/crates/wasmi/src/engine/executor/instrs/copy.rs @@ -40,7 +40,9 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> { /// Executes an [`Instruction::CopyImm32`]. #[inline(always)] pub fn execute_copy_imm32(&mut self, result: Register, value: AnyConst32) { - self.execute_copy_impl(result, value, |_, value| UntypedValue::from(value.to_u32())) + self.execute_copy_impl(result, value, |_, value| { + UntypedValue::from(u32::from(value)) + }) } /// Executes an [`Instruction::CopyI64Imm32`]. diff --git a/crates/wasmi/src/engine/executor/instrs/return_.rs b/crates/wasmi/src/engine/executor/instrs/return_.rs index 6215e5030e..b0acd37032 100644 --- a/crates/wasmi/src/engine/executor/instrs/return_.rs +++ b/crates/wasmi/src/engine/executor/instrs/return_.rs @@ -140,7 +140,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> { /// Execute an [`Instruction::ReturnImm32`] returning a single 32-bit value. #[inline(always)] pub fn execute_return_imm32(&mut self, value: AnyConst32) -> ReturnOutcome { - self.execute_return_value(value, |_, value| value.to_u32().into()) + self.execute_return_value(value, |_, value| u32::from(value).into()) } /// Execute an [`Instruction::ReturnI64Imm32`] returning a single 32-bit encoded `i64` value. diff --git a/crates/wasmi/src/engine/executor/instrs/select.rs b/crates/wasmi/src/engine/executor/instrs/select.rs index ed68b2dbb7..830e2167be 100644 --- a/crates/wasmi/src/engine/executor/instrs/select.rs +++ b/crates/wasmi/src/engine/executor/instrs/select.rs @@ -30,7 +30,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> { addr.add(1); match *addr.get() { Instruction::Register(register) => self.get_register(register), - Instruction::Const32(value) => UntypedValue::from(value.to_u32()), + Instruction::Const32(value) => UntypedValue::from(u32::from(value)), Instruction::I64Const32(value) => UntypedValue::from(i64::from(value)), Instruction::F64Const32(value) => UntypedValue::from(f64::from(value)), unexpected => unreachable!( @@ -79,7 +79,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> { /// Executes an [`Instruction::SelectImm32`]. pub fn execute_select_imm32(&mut self, result: Register, lhs: AnyConst32) { let (condition, rhs) = fetch_select_imm_param!(self, SelectImm32); - self.execute_select_impl(result, condition, |_| lhs.to_u32(), |_| rhs.to_u32()) + self.execute_select_impl(result, condition, |_| u32::from(lhs), |_| u32::from(rhs)) } /// Executes an [`Instruction::SelectI64Imm32`]. diff --git a/crates/wasmi/src/engine/translator/instr_encoder.rs b/crates/wasmi/src/engine/translator/instr_encoder.rs index 761474a9b1..0de5e5d2c9 100644 --- a/crates/wasmi/src/engine/translator/instr_encoder.rs +++ b/crates/wasmi/src/engine/translator/instr_encoder.rs @@ -352,11 +352,11 @@ impl InstrEncoder { TypedProvider::Const(value) => match value.ty() { ValueType::I32 => Instruction::copy_imm32(result, i32::from(value)), ValueType::F32 => Instruction::copy_imm32(result, f32::from(value)), - ValueType::I64 => match >::from_i64(i64::from(value)) { + ValueType::I64 => match >::try_from(i64::from(value)).ok() { Some(value) => Instruction::copy_i64imm32(result, value), None => copy_imm(stack, result, value)?, }, - ValueType::F64 => match >::from_f64(f64::from(value)) { + ValueType::F64 => match >::try_from(f64::from(value)).ok() { Some(value) => Instruction::copy_f64imm32(result, value), None => copy_imm(stack, result, value)?, }, @@ -545,12 +545,12 @@ impl InstrEncoder { [TypedProvider::Register(reg)] => Instruction::return_reg(*reg), [TypedProvider::Const(value)] => match value.ty() { ValueType::I32 => Instruction::return_imm32(i32::from(*value)), - ValueType::I64 => match >::from_i64(i64::from(*value)) { + ValueType::I64 => match >::try_from(i64::from(*value)).ok() { Some(value) => Instruction::return_i64imm32(value), None => Instruction::return_reg(stack.alloc_const(*value)?), }, ValueType::F32 => Instruction::return_imm32(F32::from(*value)), - ValueType::F64 => match >::from_f64(f64::from(*value)) { + ValueType::F64 => match >::try_from(f64::from(*value)).ok() { Some(value) => Instruction::return_f64imm32(value), None => Instruction::return_reg(stack.alloc_const(*value)?), }, @@ -613,12 +613,12 @@ impl InstrEncoder { [TypedProvider::Register(reg)] => Instruction::return_nez_reg(condition, *reg), [TypedProvider::Const(value)] => match value.ty() { ValueType::I32 => Instruction::return_nez_imm32(condition, i32::from(*value)), - ValueType::I64 => match >::from_i64(i64::from(*value)) { + ValueType::I64 => match >::try_from(i64::from(*value)).ok() { Some(value) => Instruction::return_nez_i64imm32(condition, value), None => Instruction::return_nez_reg(condition, stack.alloc_const(*value)?), }, ValueType::F32 => Instruction::return_nez_imm32(condition, F32::from(*value)), - ValueType::F64 => match >::from_f64(f64::from(*value)) { + ValueType::F64 => match >::try_from(f64::from(*value)).ok() { Some(value) => Instruction::return_nez_f64imm32(condition, value), None => Instruction::return_nez_reg(condition, stack.alloc_const(*value)?), }, diff --git a/crates/wasmi/src/engine/translator/mod.rs b/crates/wasmi/src/engine/translator/mod.rs index 55240d5775..1b329112fb 100644 --- a/crates/wasmi/src/engine/translator/mod.rs +++ b/crates/wasmi/src/engine/translator/mod.rs @@ -1641,7 +1641,7 @@ impl<'parser> FuncTranslator<'parser> { let offset = Self::memarg_offset(memarg); match self.alloc.stack.pop() { TypedProvider::Register(ptr) => { - if let Some(offset) = >::from_u32(offset) { + if let Ok(offset) = >::try_from(offset) { let result = self.alloc.stack.push_dynamic()?; self.push_fueled_instr( make_instr_offset16(result, ptr, offset), diff --git a/crates/wasmi/src/engine/translator/tests/op/binary/i32_add.rs b/crates/wasmi/src/engine/translator/tests/op/binary/i32_add.rs index 395d1144ad..7a5c0e86e5 100644 --- a/crates/wasmi/src/engine/translator/tests/op/binary/i32_add.rs +++ b/crates/wasmi/src/engine/translator/tests/op/binary/i32_add.rs @@ -56,7 +56,7 @@ fn consteval() { lhs, rhs, [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(lhs + rhs), + value: AnyConst32::from(lhs + rhs), }], ) } diff --git a/crates/wasmi/src/engine/translator/tests/op/binary/i32_and.rs b/crates/wasmi/src/engine/translator/tests/op/binary/i32_and.rs index 592d5d8809..c965a523d9 100644 --- a/crates/wasmi/src/engine/translator/tests/op/binary/i32_and.rs +++ b/crates/wasmi/src/engine/translator/tests/op/binary/i32_and.rs @@ -43,7 +43,7 @@ fn reg_imm_rev() { #[cfg_attr(miri, ignore)] fn reg_zero() { let expected = [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(0), + value: AnyConst32::from(0), }]; test_binary_reg_imm_with(WASM_OP, 0i32, expected).run() } @@ -52,7 +52,7 @@ fn reg_zero() { #[cfg_attr(miri, ignore)] fn reg_zero_rev() { let expected = [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(0), + value: AnyConst32::from(0), }]; test_binary_reg_imm_rev_with(WASM_OP, 0i32, expected).run() } @@ -81,7 +81,7 @@ fn consteval() { lhs, rhs, [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(lhs & rhs), + value: AnyConst32::from(lhs & rhs), }], ) } diff --git a/crates/wasmi/src/engine/translator/tests/op/binary/i32_div_s.rs b/crates/wasmi/src/engine/translator/tests/op/binary/i32_div_s.rs index 756e24070a..a395c1f5e8 100644 --- a/crates/wasmi/src/engine/translator/tests/op/binary/i32_div_s.rs +++ b/crates/wasmi/src/engine/translator/tests/op/binary/i32_div_s.rs @@ -72,7 +72,7 @@ fn consteval() { lhs, rhs, [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(lhs / rhs), + value: AnyConst32::from(lhs / rhs), }], ) } diff --git a/crates/wasmi/src/engine/translator/tests/op/binary/i32_div_u.rs b/crates/wasmi/src/engine/translator/tests/op/binary/i32_div_u.rs index b8c50d535e..430df67311 100644 --- a/crates/wasmi/src/engine/translator/tests/op/binary/i32_div_u.rs +++ b/crates/wasmi/src/engine/translator/tests/op/binary/i32_div_u.rs @@ -72,7 +72,7 @@ fn consteval() { lhs, rhs, [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(lhs / rhs), + value: AnyConst32::from(lhs / rhs), }], ) } diff --git a/crates/wasmi/src/engine/translator/tests/op/binary/i32_mul.rs b/crates/wasmi/src/engine/translator/tests/op/binary/i32_mul.rs index 906eca6745..c7ab1fc002 100644 --- a/crates/wasmi/src/engine/translator/tests/op/binary/i32_mul.rs +++ b/crates/wasmi/src/engine/translator/tests/op/binary/i32_mul.rs @@ -36,7 +36,7 @@ fn reg_imm_rev() { #[cfg_attr(miri, ignore)] fn reg_zero() { let expected = [Instruction::ReturnImm32 { - value: AnyConst32::from_u32(0), + value: AnyConst32::from(0), }]; test_binary_reg_imm_with(WASM_OP, 0_i32, expected).run() } @@ -45,7 +45,7 @@ fn reg_zero() { #[cfg_attr(miri, ignore)] fn reg_zero_rev() { let expected = [Instruction::ReturnImm32 { - value: AnyConst32::from_u32(0), + value: AnyConst32::from(0), }]; test_binary_reg_imm_rev_with(WASM_OP, 0_i32, expected).run() } @@ -74,7 +74,7 @@ fn consteval() { lhs, rhs, [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(lhs * rhs), + value: AnyConst32::from(lhs * rhs), }], ) } diff --git a/crates/wasmi/src/engine/translator/tests/op/binary/i32_or.rs b/crates/wasmi/src/engine/translator/tests/op/binary/i32_or.rs index e1cb8ec97d..cfebe93c34 100644 --- a/crates/wasmi/src/engine/translator/tests/op/binary/i32_or.rs +++ b/crates/wasmi/src/engine/translator/tests/op/binary/i32_or.rs @@ -57,7 +57,7 @@ fn reg_zero_rev() { #[cfg_attr(miri, ignore)] fn reg_ones() { let expected = [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(-1), + value: AnyConst32::from(-1), }]; test_binary_reg_imm_with(WASM_OP, -1_i32, expected).run() } @@ -66,7 +66,7 @@ fn reg_ones() { #[cfg_attr(miri, ignore)] fn reg_ones_rev() { let expected = [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(-1), + value: AnyConst32::from(-1), }]; test_binary_reg_imm_rev_with(WASM_OP, -1_i32, expected).run() } @@ -81,7 +81,7 @@ fn consteval() { lhs, rhs, [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(lhs | rhs), + value: AnyConst32::from(lhs | rhs), }], ) } diff --git a/crates/wasmi/src/engine/translator/tests/op/binary/i32_rem_s.rs b/crates/wasmi/src/engine/translator/tests/op/binary/i32_rem_s.rs index 364640ad45..e46b21b909 100644 --- a/crates/wasmi/src/engine/translator/tests/op/binary/i32_rem_s.rs +++ b/crates/wasmi/src/engine/translator/tests/op/binary/i32_rem_s.rs @@ -59,7 +59,7 @@ fn reg_zero() { #[cfg_attr(miri, ignore)] fn reg_one() { let expected = [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(0), + value: AnyConst32::from(0), }]; test_binary_reg_imm_with(WASM_OP, 1_i32, expected).run() } @@ -68,7 +68,7 @@ fn reg_one() { #[cfg_attr(miri, ignore)] fn reg_minus_one() { let expected = [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(0), + value: AnyConst32::from(0), }]; test_binary_reg_imm_with(WASM_OP, -1_i32, expected).run() } @@ -83,7 +83,7 @@ fn consteval() { lhs, rhs, [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(lhs % rhs), + value: AnyConst32::from(lhs % rhs), }], ) } @@ -98,7 +98,7 @@ fn consteval_2() { lhs, rhs, [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(0), // as mandated by the Wasm spec + value: AnyConst32::from(0), // as mandated by the Wasm spec }], ) } diff --git a/crates/wasmi/src/engine/translator/tests/op/binary/i32_rem_u.rs b/crates/wasmi/src/engine/translator/tests/op/binary/i32_rem_u.rs index 44d7f954f7..bfdc8bf9c4 100644 --- a/crates/wasmi/src/engine/translator/tests/op/binary/i32_rem_u.rs +++ b/crates/wasmi/src/engine/translator/tests/op/binary/i32_rem_u.rs @@ -59,7 +59,7 @@ fn reg_zero() { #[cfg_attr(miri, ignore)] fn reg_one() { let expected = [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(0), + value: AnyConst32::from(0), }]; test_binary_reg_imm_with(WASM_OP, 1_i32, expected).run() } @@ -74,7 +74,7 @@ fn consteval() { lhs, rhs, [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(lhs % rhs), + value: AnyConst32::from(lhs % rhs), }], ) } diff --git a/crates/wasmi/src/engine/translator/tests/op/binary/i32_rotl.rs b/crates/wasmi/src/engine/translator/tests/op/binary/i32_rotl.rs index eb5e965f52..6b8f10ddbf 100644 --- a/crates/wasmi/src/engine/translator/tests/op/binary/i32_rotl.rs +++ b/crates/wasmi/src/engine/translator/tests/op/binary/i32_rotl.rs @@ -56,7 +56,7 @@ fn reg_1_after_mod32() { #[cfg_attr(miri, ignore)] fn zero_reg() { let expected = [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(0_i32), + value: AnyConst32::from(0_i32), }]; test_binary_reg_imm_rev_with(WASM_OP, 0_i32, expected).run() } @@ -65,7 +65,7 @@ fn zero_reg() { #[cfg_attr(miri, ignore)] fn minus_one_reg() { let expected = [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(-1_i32), + value: AnyConst32::from(-1_i32), }]; test_binary_reg_imm_rev_with(WASM_OP, -1_i32, expected).run() } @@ -80,7 +80,7 @@ fn consteval() { lhs, rhs, [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(lhs.rotate_left(rhs as u32)), + value: AnyConst32::from(lhs.rotate_left(rhs as u32)), }], ) } diff --git a/crates/wasmi/src/engine/translator/tests/op/binary/i32_rotr.rs b/crates/wasmi/src/engine/translator/tests/op/binary/i32_rotr.rs index 7e76fbf4a1..018b045b34 100644 --- a/crates/wasmi/src/engine/translator/tests/op/binary/i32_rotr.rs +++ b/crates/wasmi/src/engine/translator/tests/op/binary/i32_rotr.rs @@ -56,7 +56,7 @@ fn reg_1_after_mod32() { #[cfg_attr(miri, ignore)] fn zero_reg() { let expected = [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(0_i32), + value: AnyConst32::from(0_i32), }]; test_binary_reg_imm_rev_with(WASM_OP, 0_i32, expected).run() } @@ -65,7 +65,7 @@ fn zero_reg() { #[cfg_attr(miri, ignore)] fn minus_one_reg() { let expected = [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(-1_i32), + value: AnyConst32::from(-1_i32), }]; test_binary_reg_imm_rev_with(WASM_OP, -1_i32, expected).run() } @@ -80,7 +80,7 @@ fn consteval() { lhs, rhs, [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(lhs.rotate_right(rhs as u32)), + value: AnyConst32::from(lhs.rotate_right(rhs as u32)), }], ) } diff --git a/crates/wasmi/src/engine/translator/tests/op/binary/i32_shl.rs b/crates/wasmi/src/engine/translator/tests/op/binary/i32_shl.rs index dce1f4fe6b..9fb912efc9 100644 --- a/crates/wasmi/src/engine/translator/tests/op/binary/i32_shl.rs +++ b/crates/wasmi/src/engine/translator/tests/op/binary/i32_shl.rs @@ -56,7 +56,7 @@ fn reg_1_after_mod32() { #[cfg_attr(miri, ignore)] fn zero_reg() { let expected = [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(0_i32), + value: AnyConst32::from(0_i32), }]; test_binary_reg_imm_rev_with(WASM_OP, 0_i32, expected).run() } @@ -71,7 +71,7 @@ fn consteval() { lhs, rhs, [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(lhs << rhs), + value: AnyConst32::from(lhs << rhs), }], ) } diff --git a/crates/wasmi/src/engine/translator/tests/op/binary/i32_shr_s.rs b/crates/wasmi/src/engine/translator/tests/op/binary/i32_shr_s.rs index b85c9aceca..1718c5091d 100644 --- a/crates/wasmi/src/engine/translator/tests/op/binary/i32_shr_s.rs +++ b/crates/wasmi/src/engine/translator/tests/op/binary/i32_shr_s.rs @@ -56,7 +56,7 @@ fn reg_1_after_mod32() { #[cfg_attr(miri, ignore)] fn zero_reg() { let expected = [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(0_i32), + value: AnyConst32::from(0_i32), }]; test_binary_reg_imm_rev_with(WASM_OP, 0_i32, expected).run() } @@ -65,7 +65,7 @@ fn zero_reg() { #[cfg_attr(miri, ignore)] fn minus_one_reg() { let expected = [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(-1_i32), + value: AnyConst32::from(-1_i32), }]; test_binary_reg_imm_rev_with(WASM_OP, -1_i32, expected).run() } @@ -80,7 +80,7 @@ fn consteval() { lhs, rhs, [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(lhs >> rhs), + value: AnyConst32::from(lhs >> rhs), }], ) } diff --git a/crates/wasmi/src/engine/translator/tests/op/binary/i32_shr_u.rs b/crates/wasmi/src/engine/translator/tests/op/binary/i32_shr_u.rs index 5971b5243f..56523457c8 100644 --- a/crates/wasmi/src/engine/translator/tests/op/binary/i32_shr_u.rs +++ b/crates/wasmi/src/engine/translator/tests/op/binary/i32_shr_u.rs @@ -56,7 +56,7 @@ fn reg_1_after_mod32() { #[cfg_attr(miri, ignore)] fn zero_reg() { let expected = [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(0_i32), + value: AnyConst32::from(0_i32), }]; test_binary_reg_imm_rev_with(WASM_OP, 0_i32, expected).run() } @@ -71,7 +71,7 @@ fn consteval() { lhs, rhs, [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(lhs >> rhs), + value: AnyConst32::from(lhs >> rhs), }], ) } diff --git a/crates/wasmi/src/engine/translator/tests/op/binary/i32_sub.rs b/crates/wasmi/src/engine/translator/tests/op/binary/i32_sub.rs index d7ad77dde7..e47ff2add0 100644 --- a/crates/wasmi/src/engine/translator/tests/op/binary/i32_sub.rs +++ b/crates/wasmi/src/engine/translator/tests/op/binary/i32_sub.rs @@ -6,7 +6,7 @@ const WASM_OP: WasmOp = WasmOp::binary(WasmType::I32, "sub"); #[cfg_attr(miri, ignore)] fn same_reg() { let expected = [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(0), + value: AnyConst32::from(0), }]; test_binary_same_reg(WASM_OP, expected) } @@ -58,7 +58,7 @@ fn consteval() { lhs, rhs, [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(lhs - rhs), + value: AnyConst32::from(lhs - rhs), }], ) } diff --git a/crates/wasmi/src/engine/translator/tests/op/binary/i32_xor.rs b/crates/wasmi/src/engine/translator/tests/op/binary/i32_xor.rs index 6dc8d6a4fc..5cd98f6fad 100644 --- a/crates/wasmi/src/engine/translator/tests/op/binary/i32_xor.rs +++ b/crates/wasmi/src/engine/translator/tests/op/binary/i32_xor.rs @@ -6,7 +6,7 @@ const WASM_OP: WasmOp = WasmOp::binary(WasmType::I32, "xor"); #[cfg_attr(miri, ignore)] fn same_reg() { let expected = [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(0), + value: AnyConst32::from(0), }]; test_binary_same_reg(WASM_OP, expected) } @@ -65,7 +65,7 @@ fn consteval() { lhs, rhs, [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(lhs ^ rhs), + value: AnyConst32::from(lhs ^ rhs), }], ) } diff --git a/crates/wasmi/src/engine/translator/tests/op/binary/mod.rs b/crates/wasmi/src/engine/translator/tests/op/binary/mod.rs index 89aa53ea41..7d61f906a9 100644 --- a/crates/wasmi/src/engine/translator/tests/op/binary/mod.rs +++ b/crates/wasmi/src/engine/translator/tests/op/binary/mod.rs @@ -57,6 +57,6 @@ mod i64_xor; /// /// If the `value` cannot be converted into `f32` losslessly. fn return_f64imm32_instr(value: f64) -> Instruction { - let const32 = >::from_f64(value).expect("value must be 32-bit encodable"); + let const32 = >::try_from(value).expect("value must be 32-bit encodable"); Instruction::return_f64imm32(const32) } diff --git a/crates/wasmi/src/engine/translator/tests/op/block.rs b/crates/wasmi/src/engine/translator/tests/op/block.rs index 7264242c0f..c17367b95e 100644 --- a/crates/wasmi/src/engine/translator/tests/op/block.rs +++ b/crates/wasmi/src/engine/translator/tests/op/block.rs @@ -198,7 +198,7 @@ fn branched_block_1_imm_i32() { fn branched_block_1_imm_i64imm32() { fn test_for_i64imm32(value: i64) { let const32 = - >::from_i64(value).expect("value must be 32-bit encodable for this test"); + >::try_from(value).expect("value must be 32-bit encodable for this test"); testcase_branched_block_1_imm::(value) .expect_func_instrs([ Instruction::copy_i64imm32(Register::from_i16(0), const32), @@ -261,7 +261,7 @@ fn branched_block_1_imm_f32() { #[cfg_attr(miri, ignore)] fn branched_block_1_imm_f64imm32() { fn test_for_f64imm32(value: f64) { - let const32 = >::from_f64(value) + let const32 = >::try_from(value) .expect("value must be losslessly 32-bit encodable for this test"); testcase_branched_block_1_imm::(value) .expect_func_instrs([ diff --git a/crates/wasmi/src/engine/translator/tests/op/cmp/f64_eq.rs b/crates/wasmi/src/engine/translator/tests/op/cmp/f64_eq.rs index 017dc0c561..a4113b968e 100644 --- a/crates/wasmi/src/engine/translator/tests/op/cmp/f64_eq.rs +++ b/crates/wasmi/src/engine/translator/tests/op/cmp/f64_eq.rs @@ -55,7 +55,7 @@ fn consteval() { 1.0, 1.0, [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(1), + value: AnyConst32::from(1), }], ); test_binary_consteval( @@ -63,7 +63,7 @@ fn consteval() { 0.0, 1.0, [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(0), + value: AnyConst32::from(0), }], ); } diff --git a/crates/wasmi/src/engine/translator/tests/op/cmp/i32_ne.rs b/crates/wasmi/src/engine/translator/tests/op/cmp/i32_ne.rs index f127e46120..1ea0f52d99 100644 --- a/crates/wasmi/src/engine/translator/tests/op/cmp/i32_ne.rs +++ b/crates/wasmi/src/engine/translator/tests/op/cmp/i32_ne.rs @@ -6,7 +6,7 @@ const WASM_OP: WasmOp = WasmOp::cmp(WasmType::I32, "ne"); #[cfg_attr(miri, ignore)] fn same_reg() { let expected = [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(0), + value: AnyConst32::from(0), }]; test_binary_same_reg(WASM_OP, expected) } @@ -49,7 +49,7 @@ fn consteval() { 1, 1, [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(0), + value: AnyConst32::from(0), }], ); test_binary_consteval( @@ -57,7 +57,7 @@ fn consteval() { 42, 5, [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(1), + value: AnyConst32::from(1), }], ); } diff --git a/crates/wasmi/src/engine/translator/tests/op/cmp/i64_ne.rs b/crates/wasmi/src/engine/translator/tests/op/cmp/i64_ne.rs index ff8001d102..b6a1c89602 100644 --- a/crates/wasmi/src/engine/translator/tests/op/cmp/i64_ne.rs +++ b/crates/wasmi/src/engine/translator/tests/op/cmp/i64_ne.rs @@ -6,7 +6,7 @@ const WASM_OP: WasmOp = WasmOp::cmp(WasmType::I64, "ne"); #[cfg_attr(miri, ignore)] fn same_reg() { let expected = [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(0), + value: AnyConst32::from(0), }]; test_binary_same_reg(WASM_OP, expected) } @@ -49,7 +49,7 @@ fn consteval() { 1, 1, [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(0), + value: AnyConst32::from(0), }], ); test_binary_consteval( @@ -57,7 +57,7 @@ fn consteval() { 42, 5, [Instruction::ReturnImm32 { - value: AnyConst32::from_i32(1), + value: AnyConst32::from(1), }], ); } diff --git a/crates/wasmi/src/engine/translator/tests/op/global_set.rs b/crates/wasmi/src/engine/translator/tests/op/global_set.rs index dd72f5b10e..3c4ac32cbf 100644 --- a/crates/wasmi/src/engine/translator/tests/op/global_set.rs +++ b/crates/wasmi/src/engine/translator/tests/op/global_set.rs @@ -99,8 +99,8 @@ fn test_i32imm16(value: i32) { ) "#, )); - let imm16 = >::from_i32(value) - .unwrap_or_else(|| panic!("cannot convert `value` to 16-bit encoding: {value}")); + let imm16 = >::try_from(value) + .unwrap_or_else(|_| panic!("cannot convert `value` to 16-bit encoding: {value}")); TranslationTest::new(wasm) .expect_func_instrs([ Instruction::global_set_i32imm16(GlobalIdx::from(0), imm16), @@ -133,8 +133,8 @@ fn test_i64imm16(value: i64) { ) "#, )); - let imm16 = >::from_i64(value) - .unwrap_or_else(|| panic!("cannot convert `value` to 16-bit encoding: {value}")); + let imm16 = >::try_from(value) + .unwrap_or_else(|_| panic!("cannot convert `value` to 16-bit encoding: {value}")); TranslationTest::new(wasm) .expect_func_instrs([ Instruction::global_set_i64imm16(GlobalIdx::from(0), imm16), diff --git a/crates/wasmi/src/engine/translator/tests/op/mod.rs b/crates/wasmi/src/engine/translator/tests/op/mod.rs index 89cc9cfa7c..a1f808ec46 100644 --- a/crates/wasmi/src/engine/translator/tests/op/mod.rs +++ b/crates/wasmi/src/engine/translator/tests/op/mod.rs @@ -59,8 +59,8 @@ use super::{ #[track_caller] #[allow(dead_code)] fn i32imm16(value: i32) -> Const16 { - >::from_i32(value) - .unwrap_or_else(|| panic!("value must be 16-bit encodable: {}", value)) + >::try_from(value) + .unwrap_or_else(|_| panic!("value must be 16-bit encodable: {}", value)) } /// Creates an [`Const32`] from the given `u32` value. @@ -70,8 +70,8 @@ fn i32imm16(value: i32) -> Const16 { /// If the `value` cannot be converted into `u32` losslessly. #[track_caller] fn u32imm16(value: u32) -> Const16 { - >::from_u32(value) - .unwrap_or_else(|| panic!("value must be 16-bit encodable: {}", value)) + >::try_from(value) + .unwrap_or_else(|_| panic!("value must be 16-bit encodable: {}", value)) } /// Creates an [`Const32`] from the given `i64` value. @@ -81,8 +81,8 @@ fn u32imm16(value: u32) -> Const16 { /// If the `value` cannot be converted into `i32` losslessly. #[track_caller] fn i64imm32(value: i64) -> Const32 { - >::from_i64(value) - .unwrap_or_else(|| panic!("value must be 32-bit encodable: {}", value)) + >::try_from(value) + .unwrap_or_else(|_| panic!("value must be 32-bit encodable: {}", value)) } /// Creates an [`Const32`] from the given `i64` value. @@ -92,8 +92,8 @@ fn i64imm32(value: i64) -> Const32 { /// If the `value` cannot be converted into `i32` losslessly. #[track_caller] fn f64imm32(value: f64) -> Const32 { - >::from_f64(value) - .unwrap_or_else(|| panic!("value must be 32-bit encodable: {}", value)) + >::try_from(value) + .unwrap_or_else(|_| panic!("value must be 32-bit encodable: {}", value)) } /// Creates an [`Instruction::I64Imm32`] from the given `i64` value. diff --git a/crates/wasmi/src/engine/translator/tests/wasm_type.rs b/crates/wasmi/src/engine/translator/tests/wasm_type.rs index 62bbbe946d..29168a59bd 100644 --- a/crates/wasmi/src/engine/translator/tests/wasm_type.rs +++ b/crates/wasmi/src/engine/translator/tests/wasm_type.rs @@ -36,7 +36,7 @@ impl WasmType for u64 { const VALUE_TYPE: ValueType = ValueType::I64; fn return_imm_instr(&self) -> Instruction { - match >::from_i64(*self as i64) { + match >::try_from(*self as i64).ok() { Some(value) => Instruction::return_i64imm32(value), None => Instruction::return_reg(Register::from_i16(-1)), } @@ -48,7 +48,7 @@ impl WasmType for i64 { const VALUE_TYPE: ValueType = ValueType::I64; fn return_imm_instr(&self) -> Instruction { - match >::from_i64(*self) { + match >::try_from(*self).ok() { Some(value) => Instruction::return_i64imm32(value), None => Instruction::return_reg(Register::from_i16(-1)), } @@ -69,7 +69,7 @@ impl WasmType for f64 { const VALUE_TYPE: ValueType = ValueType::F64; fn return_imm_instr(&self) -> Instruction { - match >::from_f64(*self) { + match >::try_from(*self).ok() { Some(value) => Instruction::return_f64imm32(value), None => Instruction::return_reg(Register::from_i16(-1)), } diff --git a/crates/wasmi/src/engine/translator/utils.rs b/crates/wasmi/src/engine/translator/utils.rs index 1909208bca..4fb846ad94 100644 --- a/crates/wasmi/src/engine/translator/utils.rs +++ b/crates/wasmi/src/engine/translator/utils.rs @@ -122,7 +122,7 @@ impl Provider> { /// procedures for certain Wasm `table` instructions. pub fn new(provider: TypedProvider, stack: &mut ValueStack) -> Result { match provider { - TypedProvider::Const(value) => match Const16::from_u32(u32::from(value)) { + TypedProvider::Const(value) => match Const16::try_from(u32::from(value)).ok() { Some(value) => Ok(Self::Const(value)), None => { let register = stack.alloc_const(value)?; diff --git a/crates/wasmi/src/engine/translator/visit.rs b/crates/wasmi/src/engine/translator/visit.rs index 2e76955504..882bd1a048 100644 --- a/crates/wasmi/src/engine/translator/visit.rs +++ b/crates/wasmi/src/engine/translator/visit.rs @@ -664,7 +664,7 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> { let provider_params = &mut self.alloc.buffer; self.alloc.stack.pop_n(params.len(), provider_params); let table_params = match index { - TypedProvider::Const(index) => match >::from_u32(u32::from(index)) { + TypedProvider::Const(index) => match >::try_from(u32::from(index)).ok() { Some(index) => { // Case: the index is encodable as 16-bit constant value // which allows us to use an optimized instruction. @@ -736,7 +736,7 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> { let provider_params = &mut self.alloc.buffer; self.alloc.stack.pop_n(params.len(), provider_params); let table_params = match index { - TypedProvider::Const(index) => match >::from_u32(u32::from(index)) { + TypedProvider::Const(index) => match >::try_from(u32::from(index)).ok() { Some(index) => { // Case: the index is encodable as 16-bit constant value // which allows us to use an optimized instruction. @@ -852,7 +852,7 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> { debug_assert_eq!(global_type.content(), input.ty()); match global_type.content() { ValueType::I32 => { - if let Some(value) = Const16::from_i32(i32::from(input)) { + if let Ok(value) = Const16::try_from(i32::from(input)) { self.push_fueled_instr( Instruction::global_set_i32imm16(global, value), FuelCosts::entity, @@ -861,7 +861,7 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> { } } ValueType::I64 => { - if let Some(value) = Const16::from_i64(i64::from(input)) { + if let Ok(value) = Const16::try_from(i64::from(input)) { self.push_fueled_instr( Instruction::global_set_i64imm16(global, value), FuelCosts::entity, From 42371ac0b5235ca02ebda7836c6781a989e2c858 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Sun, 3 Dec 2023 13:57:16 +0100 Subject: [PATCH 4/7] Add `divrem` benchmark test (#827) * fix naming in fure benchmark test * add benchmark test for divrem --- crates/wasmi/benches/benches.rs | 23 ++++++++++++++++++++-- crates/wasmi/benches/wat/divrem.wat | 30 +++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 crates/wasmi/benches/wat/divrem.wat diff --git a/crates/wasmi/benches/benches.rs b/crates/wasmi/benches/benches.rs index 231e5cd9d2..dc2a02ebc0 100644 --- a/crates/wasmi/benches/benches.rs +++ b/crates/wasmi/benches/benches.rs @@ -73,6 +73,7 @@ criterion_group! { bench_execute_recursive_trap, bench_execute_host_calls, bench_execute_fuse, + bench_execute_divrem, bench_execute_fibonacci, bench_execute_recursive_is_even, bench_execute_memory_sum, @@ -851,20 +852,38 @@ fn bench_execute_fuse(c: &mut Criterion) { let (mut store, instance) = load_instance_from_wat(include_bytes!("wat/fuse.wat")); let mut bench_fuse = |bench_id: &str, func_name: &str, input: i32| { c.bench_function(bench_id, |b| { - let fib = instance + let test = instance .get_export(&store, func_name) .and_then(Extern::into_func) .unwrap() .typed::(&store) .unwrap(); b.iter(|| { - assert_eq!(fib.call(&mut store, input).unwrap(), input); + assert_eq!(test.call(&mut store, input).unwrap(), input); }); }); }; bench_fuse("execute/fuse", "test", 1_000_000); } +fn bench_execute_divrem(c: &mut Criterion) { + let (mut store, instance) = load_instance_from_wat(include_bytes!("wat/divrem.wat")); + let mut bench_fuse = |bench_id: &str, func_name: &str, input: i32| { + c.bench_function(bench_id, |b| { + let fib = instance + .get_export(&store, func_name) + .and_then(Extern::into_func) + .unwrap() + .typed::(&store) + .unwrap(); + b.iter(|| { + assert_eq!(fib.call(&mut store, input).unwrap(), 0); + }); + }); + }; + bench_fuse("execute/divrem", "test", 250_000); +} + fn bench_execute_fibonacci(c: &mut Criterion) { const fn fib(n: i64) -> i64 { if n <= 1 { diff --git a/crates/wasmi/benches/wat/divrem.wat b/crates/wasmi/benches/wat/divrem.wat new file mode 100644 index 0000000000..8fc0ac455a --- /dev/null +++ b/crates/wasmi/benches/wat/divrem.wat @@ -0,0 +1,30 @@ +(module + (func (export "test") (param $n i32) (result i32) + (local $m i64) + (local $tmp32 i32) + (local $tmp64 i64) + (loop $continue + ;; n -= 1 + (local.set $n + (i32.sub + (local.get $n) + (i32.const 1) + ) + ) + ;; m = n + (local.set $m (i64.extend_i32_u (local.get $n))) + ;; execute a bunch of div and rem instructions with immediate `rhs` values + (local.set $tmp32 (i32.div_s (local.get $n) (i32.const 3))) + (local.set $tmp32 (i32.div_u (local.get $n) (i32.const 3))) + (local.set $tmp32 (i32.rem_s (local.get $n) (i32.const 3))) + (local.set $tmp32 (i32.rem_u (local.get $n) (i32.const 3))) + (local.set $tmp64 (i64.div_s (local.get $m) (i64.const 3))) + (local.set $tmp64 (i64.div_u (local.get $m) (i64.const 3))) + (local.set $tmp64 (i64.rem_s (local.get $m) (i64.const 3))) + (local.set $tmp64 (i64.rem_u (local.get $m) (i64.const 3))) + ;; continue if $n != 0 + (br_if $continue (local.get $n)) + ) + (return (local.get $n)) + ) +) From 63b1a63169c44b287eeb2ef4ef2fda1ac9cee3b4 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Sun, 3 Dec 2023 14:28:54 +0100 Subject: [PATCH 5/7] Optimize divrem with non-zero immediate `rhs` values (#825) * optimize divrem with non-zero immediate rhs values * rename DivRemImm -> DivRemExt * add docs to DivRemExt trait * use macro to generate divrem instr constructors * refactor AnyConst{16,32}, Const16 and Const32 APIs They now offer all their API via From and TryFrom impls if possible. * generalize Const16::is_zero method * fix SAFETY comment * fix naming in fure benchmark test * add benchmark test for divrem --- crates/wasmi/src/engine/bytecode/construct.rs | 33 ++++-- crates/wasmi/src/engine/bytecode/mod.rs | 17 +-- crates/wasmi/src/engine/executor/instrs.rs | 33 ++++-- .../src/engine/executor/instrs/binary.rs | 109 ++++++++++++++++-- crates/wasmi/src/engine/translator/mod.rs | 15 ++- .../src/engine/translator/relink_result.rs | 28 ++--- .../engine/translator/tests/display_wasm.rs | 24 +++- .../translator/tests/op/binary/i32_div_s.rs | 2 +- .../translator/tests/op/binary/i32_div_u.rs | 2 +- .../translator/tests/op/binary/i32_rem_s.rs | 2 +- .../translator/tests/op/binary/i32_rem_u.rs | 2 +- .../translator/tests/op/binary/i64_div_s.rs | 2 +- .../translator/tests/op/binary/i64_div_u.rs | 2 +- .../translator/tests/op/binary/i64_rem_s.rs | 2 +- .../translator/tests/op/binary/i64_rem_u.rs | 3 +- .../engine/translator/tests/op/binary/mod.rs | 21 ++++ crates/wasmi/src/engine/translator/visit.rs | 9 +- 17 files changed, 234 insertions(+), 72 deletions(-) diff --git a/crates/wasmi/src/engine/bytecode/construct.rs b/crates/wasmi/src/engine/bytecode/construct.rs index 06ec49224c..f4acf2dc11 100644 --- a/crates/wasmi/src/engine/bytecode/construct.rs +++ b/crates/wasmi/src/engine/bytecode/construct.rs @@ -29,6 +29,7 @@ use super::{ TableIdx, UnaryInstr, }; +use core::num::{NonZeroI32, NonZeroI64, NonZeroU32, NonZeroU64}; macro_rules! constructor_for { ( @@ -264,6 +265,30 @@ constructor_for_branch_binop_imm! { fn branch_i64_ge_u_imm(u64) -> Self::BranchI64GeUImm; } +macro_rules! constructor_for_branch_binop_imm { + ( $( fn $name:ident($ty:ty) -> Self::$op_code:ident; )* ) => { + impl Instruction { + $( + #[doc = concat!("Creates a new [`Instruction::", stringify!($op_code), "`].")] + pub fn $name(result: Register, lhs: Register, rhs: impl Into>) -> Self { + Self::$op_code(BinInstrImm16::new(result, lhs, rhs.into())) + } + )* + } + } +} +constructor_for_branch_binop_imm! { + fn i32_div_s_imm16(NonZeroI32) -> Self::I32DivSImm16; + fn i32_div_u_imm16(NonZeroU32) -> Self::I32DivUImm16; + fn i32_rem_s_imm16(NonZeroI32) -> Self::I32RemSImm16; + fn i32_rem_u_imm16(NonZeroU32) -> Self::I32RemUImm16; + + fn i64_div_s_imm16(NonZeroI64) -> Self::I64DivSImm16; + fn i64_div_u_imm16(NonZeroU64) -> Self::I64DivUImm16; + fn i64_rem_s_imm16(NonZeroI64) -> Self::I64RemSImm16; + fn i64_rem_u_imm16(NonZeroU64) -> Self::I64RemUImm16; +} + impl Instruction { /// Creates a new [`Instruction::Const32`] from the given `value`. pub fn const32(value: impl Into) -> Self { @@ -1541,35 +1566,27 @@ impl Instruction { // Integer Division & Remainder fn i32_div_u(binary) -> Self::I32DivU; - fn i32_div_u_imm16(binary_u32imm16) -> Self::I32DivUImm16; fn i32_div_u_imm16_rev(binary_u32imm16_rev) -> Self::I32DivUImm16Rev; fn i64_div_u(binary) -> Self::I64DivU; - fn i64_div_u_imm16(binary_u64imm16) -> Self::I64DivUImm16; fn i64_div_u_imm16_rev(binary_u64imm16_rev) -> Self::I64DivUImm16Rev; fn i32_div_s(binary) -> Self::I32DivS; - fn i32_div_s_imm16(binary_i32imm16) -> Self::I32DivSImm16; fn i32_div_s_imm16_rev(binary_i32imm16_rev) -> Self::I32DivSImm16Rev; fn i64_div_s(binary) -> Self::I64DivS; - fn i64_div_s_imm16(binary_i64imm16) -> Self::I64DivSImm16; fn i64_div_s_imm16_rev(binary_i64imm16_rev) -> Self::I64DivSImm16Rev; fn i32_rem_u(binary) -> Self::I32RemU; - fn i32_rem_u_imm16(binary_u32imm16) -> Self::I32RemUImm16; fn i32_rem_u_imm16_rev(binary_u32imm16_rev) -> Self::I32RemUImm16Rev; fn i64_rem_u(binary) -> Self::I64RemU; - fn i64_rem_u_imm16(binary_u64imm16) -> Self::I64RemUImm16; fn i64_rem_u_imm16_rev(binary_u64imm16_rev) -> Self::I64RemUImm16Rev; fn i32_rem_s(binary) -> Self::I32RemS; - fn i32_rem_s_imm16(binary_i32imm16) -> Self::I32RemSImm16; fn i32_rem_s_imm16_rev(binary_i32imm16_rev) -> Self::I32RemSImm16Rev; fn i64_rem_s(binary) -> Self::I64RemS; - fn i64_rem_s_imm16(binary_i64imm16) -> Self::I64RemSImm16; fn i64_rem_s_imm16_rev(binary_i64imm16_rev) -> Self::I64RemSImm16Rev; // Integer Bitwise Logic diff --git a/crates/wasmi/src/engine/bytecode/mod.rs b/crates/wasmi/src/engine/bytecode/mod.rs index bd040ac4bc..e9284e99a0 100644 --- a/crates/wasmi/src/engine/bytecode/mod.rs +++ b/crates/wasmi/src/engine/bytecode/mod.rs @@ -40,6 +40,7 @@ pub(crate) use self::{ }, }; use crate::engine::{CompiledFunc, TranslationError}; +use core::num::{NonZeroI32, NonZeroI64, NonZeroU32, NonZeroU64}; use wasmi_core::TrapCode; /// A `wasmi` instruction. @@ -2562,14 +2563,14 @@ pub enum Instruction { /// /// - Optimized variant of [`Instruction::I32DivS`] for 16-bit constant values. /// - Guarantees that the right-hand side operand is not zero. - I32DivSImm16(BinInstrImm16), + I32DivSImm16(BinInstrImm16), /// `i64` singed-division immediate instruction: `r0 = r1 / c0` /// /// # Note /// /// - Optimized variant of [`Instruction::I64DivS`] for 16-bit constant values. /// - Guarantees that the right-hand side operand is not zero. - I64DivSImm16(BinInstrImm16), + I64DivSImm16(BinInstrImm16), /// `i32` singed-division immediate instruction: `r0 = c0 / r1` /// /// # Note @@ -2600,7 +2601,7 @@ pub enum Instruction { /// # Encoding /// /// Optimized variant of [`Instruction::I32DivU`] for 16-bit constant values. - I32DivUImm16(BinInstrImm16), + I32DivUImm16(BinInstrImm16), /// `i64` unsinged-division immediate instruction: `r0 = r1 / c0` /// /// # Note @@ -2610,7 +2611,7 @@ pub enum Instruction { /// # Encoding /// /// Optimized variant of [`Instruction::I64DivU`] for 16-bit constant values. - I64DivUImm16(BinInstrImm16), + I64DivUImm16(BinInstrImm16), /// `i32` unsinged-division immediate instruction: `r0 = c0 / r1` /// /// # Note @@ -2638,14 +2639,14 @@ pub enum Instruction { /// /// - Optimized variant of [`Instruction::I32RemS`] for 16-bit constant values. /// - Guarantees that the right-hand side operand is not zero. - I32RemSImm16(BinInstrImm16), + I32RemSImm16(BinInstrImm16), /// `i64` singed-remainder immediate instruction: `r0 = r1 % c0` /// /// # Note /// /// - Optimized variant of [`Instruction::I64RemS`] for 16-bit constant values. /// - Guarantees that the right-hand side operand is not zero. - I64RemSImm16(BinInstrImm16), + I64RemSImm16(BinInstrImm16), /// `i32` singed-remainder immediate instruction: `r0 = c0 % r1` /// /// # Note @@ -2673,14 +2674,14 @@ pub enum Instruction { /// /// - Optimized variant of [`Instruction::I32RemU`] for 16-bit constant values. /// - Guarantees that the right-hand side operand is not zero. - I32RemUImm16(BinInstrImm16), + I32RemUImm16(BinInstrImm16), /// `i64` singed-remainder immediate instruction: `r0 = r1 % c0` /// /// # Note /// /// - Optimized variant of [`Instruction::I64RemU`] for 16-bit constant values. /// - Guarantees that the right-hand side operand is not zero. - I64RemUImm16(BinInstrImm16), + I64RemUImm16(BinInstrImm16), /// `i32` unsigned-remainder immediate instruction: `r0 = c0 % r1` /// /// # Note diff --git a/crates/wasmi/src/engine/executor/instrs.rs b/crates/wasmi/src/engine/executor/instrs.rs index 245df45f6e..38262a2d64 100644 --- a/crates/wasmi/src/engine/executor/instrs.rs +++ b/crates/wasmi/src/engine/executor/instrs.rs @@ -719,13 +719,13 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> { Instr::I32DivSImm16(instr) => self.execute_i32_div_s_imm16(instr)?, Instr::I32DivSImm16Rev(instr) => self.execute_i32_div_s_imm16_rev(instr)?, Instr::I32DivU(instr) => self.execute_i32_div_u(instr)?, - Instr::I32DivUImm16(instr) => self.execute_i32_div_u_imm16(instr)?, + Instr::I32DivUImm16(instr) => self.execute_i32_div_u_imm16(instr), Instr::I32DivUImm16Rev(instr) => self.execute_i32_div_u_imm16_rev(instr)?, Instr::I32RemS(instr) => self.execute_i32_rem_s(instr)?, Instr::I32RemSImm16(instr) => self.execute_i32_rem_s_imm16(instr)?, Instr::I32RemSImm16Rev(instr) => self.execute_i32_rem_s_imm16_rev(instr)?, Instr::I32RemU(instr) => self.execute_i32_rem_u(instr)?, - Instr::I32RemUImm16(instr) => self.execute_i32_rem_u_imm16(instr)?, + Instr::I32RemUImm16(instr) => self.execute_i32_rem_u_imm16(instr), Instr::I32RemUImm16Rev(instr) => self.execute_i32_rem_u_imm16_rev(instr)?, Instr::I32And(instr) => self.execute_i32_and(instr), Instr::I32AndEqz(instr) => self.execute_i32_and_eqz(instr), @@ -765,13 +765,13 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> { Instr::I64DivSImm16(instr) => self.execute_i64_div_s_imm16(instr)?, Instr::I64DivSImm16Rev(instr) => self.execute_i64_div_s_imm16_rev(instr)?, Instr::I64DivU(instr) => self.execute_i64_div_u(instr)?, - Instr::I64DivUImm16(instr) => self.execute_i64_div_u_imm16(instr)?, + Instr::I64DivUImm16(instr) => self.execute_i64_div_u_imm16(instr), Instr::I64DivUImm16Rev(instr) => self.execute_i64_div_u_imm16_rev(instr)?, Instr::I64RemS(instr) => self.execute_i64_rem_s(instr)?, Instr::I64RemSImm16(instr) => self.execute_i64_rem_s_imm16(instr)?, Instr::I64RemSImm16Rev(instr) => self.execute_i64_rem_s_imm16_rev(instr)?, Instr::I64RemU(instr) => self.execute_i64_rem_u(instr)?, - Instr::I64RemUImm16(instr) => self.execute_i64_rem_u_imm16(instr)?, + Instr::I64RemUImm16(instr) => self.execute_i64_rem_u_imm16(instr), Instr::I64RemUImm16Rev(instr) => self.execute_i64_rem_u_imm16_rev(instr)?, Instr::I64And(instr) => self.execute_i64_and(instr), Instr::I64AndImm16(instr) => self.execute_i64_and_imm16(instr), @@ -1176,21 +1176,34 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> { } /// Executes a fallible generic binary [`Instruction`]. - fn try_execute_binary_imm16( + fn try_execute_divrem_imm16( &mut self, - instr: BinInstrImm16, - op: fn(UntypedValue, UntypedValue) -> Result, + instr: BinInstrImm16, + op: fn(UntypedValue, NonZeroT) -> Result, ) -> Result<(), TrapCode> where - T: From>, - UntypedValue: From, + NonZeroT: From>, { let lhs = self.get_register(instr.reg_in); - let rhs = UntypedValue::from(::from(instr.imm_in)); + let rhs = ::from(instr.imm_in); self.set_register(instr.result, op(lhs, rhs)?); self.try_next_instr() } + /// Executes a fallible generic binary [`Instruction`]. + fn execute_divrem_imm16( + &mut self, + instr: BinInstrImm16, + op: fn(UntypedValue, NonZeroT) -> UntypedValue, + ) where + NonZeroT: From>, + { + let lhs = self.get_register(instr.reg_in); + let rhs = ::from(instr.imm_in); + self.set_register(instr.result, op(lhs, rhs)); + self.next_instr() + } + /// Executes a fallible generic binary [`Instruction`] with reversed operands. fn try_execute_binary_imm16_rev( &mut self, diff --git a/crates/wasmi/src/engine/executor/instrs/binary.rs b/crates/wasmi/src/engine/executor/instrs/binary.rs index 01706cd762..4d9540e408 100644 --- a/crates/wasmi/src/engine/executor/instrs/binary.rs +++ b/crates/wasmi/src/engine/executor/instrs/binary.rs @@ -3,6 +3,7 @@ use crate::{ core::{TrapCode, UntypedValue}, engine::bytecode::{BinInstr, BinInstrImm, BinInstrImm16, Sign}, }; +use core::num::{NonZeroI32, NonZeroI64, NonZeroU32, NonZeroU64}; #[cfg(doc)] use crate::engine::bytecode::Instruction; @@ -167,28 +168,112 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> { } } -macro_rules! impl_fallible_binary_imm16 { +/// Extension trait to provide more optimized divide and remainder implementations. +pub trait DivRemExt: Sized { + /// Optimized variant of Wasm `i32.div_s` for immutable non-zero `rhs` values. + fn i32_div_s(self, rhs: NonZeroI32) -> Result; + /// Optimized variant of Wasm `i32.div_u` for immutable non-zero `rhs` values. + fn i32_div_u(self, rhs: NonZeroU32) -> Self; + /// Optimized variant of Wasm `i32.rem_s` for immutable non-zero `rhs` values. + fn i32_rem_s(self, rhs: NonZeroI32) -> Result; + /// Optimized variant of Wasm `i32.rem_u` for immutable non-zero `rhs` values. + fn i32_rem_u(self, rhs: NonZeroU32) -> Self; + + /// Optimized variant of Wasm `i64.div_s` for immutable non-zero `rhs` values. + fn i64_div_s(self, rhs: NonZeroI64) -> Result; + /// Optimized variant of Wasm `i64.div_u` for immutable non-zero `rhs` values. + fn i64_div_u(self, rhs: NonZeroU64) -> Self; + /// Optimized variant of Wasm `i64.rem_s` for immutable non-zero `rhs` values. + fn i64_rem_s(self, rhs: NonZeroI64) -> Result; + /// Optimized variant of Wasm `i64.rem_u` for immutable non-zero `rhs` values. + fn i64_rem_u(self, rhs: NonZeroU64) -> Self; +} + +impl DivRemExt for UntypedValue { + fn i32_div_s(self, rhs: NonZeroI32) -> Result { + i32::from(self) + .checked_div(rhs.get()) + .map(Self::from) + .ok_or(TrapCode::IntegerOverflow) + } + + fn i32_div_u(self, rhs: NonZeroU32) -> Self { + Self::from(u32::from(self) / rhs) + } + + fn i32_rem_s(self, rhs: NonZeroI32) -> Result { + i32::from(self) + .checked_rem(rhs.get()) + .map(Self::from) + .ok_or(TrapCode::IntegerOverflow) + } + + fn i32_rem_u(self, rhs: NonZeroU32) -> Self { + Self::from(u32::from(self) % rhs) + } + + fn i64_div_s(self, rhs: NonZeroI64) -> Result { + i64::from(self) + .checked_div(rhs.get()) + .map(Self::from) + .ok_or(TrapCode::IntegerOverflow) + } + + fn i64_div_u(self, rhs: NonZeroU64) -> Self { + Self::from(u64::from(self) / rhs) + } + + fn i64_rem_s(self, rhs: NonZeroI64) -> Result { + i64::from(self) + .checked_rem(rhs.get()) + .map(Self::from) + .ok_or(TrapCode::IntegerOverflow) + } + + fn i64_rem_u(self, rhs: NonZeroU64) -> Self { + Self::from(u64::from(self) % rhs) + } +} + +macro_rules! impl_divrem_s_imm16 { ( $( ($ty:ty, Instruction::$var_name:ident, $fn_name:ident, $op:expr) ),* $(,)? ) => { $( #[doc = concat!("Executes an [`Instruction::", stringify!($var_name), "`].")] #[inline(always)] pub fn $fn_name(&mut self, instr: BinInstrImm16<$ty>) -> Result<(), TrapCode> { - self.try_execute_binary_imm16(instr, $op) + self.try_execute_divrem_imm16(instr, $op) } )* }; } impl<'ctx, 'engine> Executor<'ctx, 'engine> { - impl_fallible_binary_imm16! { - (i32, Instruction::I32DivSImm16, execute_i32_div_s_imm16, UntypedValue::i32_div_s), - (u32, Instruction::I32DivUImm16, execute_i32_div_u_imm16, UntypedValue::i32_div_u), - (i32, Instruction::I32RemSImm16, execute_i32_rem_s_imm16, UntypedValue::i32_rem_s), - (u32, Instruction::I32RemUImm16, execute_i32_rem_u_imm16, UntypedValue::i32_rem_u), - - (i64, Instruction::I64DivSImm16, execute_i64_div_s_imm16, UntypedValue::i64_div_s), - (u64, Instruction::I64DivUImm16, execute_i64_div_u_imm16, UntypedValue::i64_div_u), - (i64, Instruction::I64RemSImm16, execute_i64_rem_s_imm16, UntypedValue::i64_rem_s), - (u64, Instruction::I64RemUImm16, execute_i64_rem_u_imm16, UntypedValue::i64_rem_u), + impl_divrem_s_imm16! { + (NonZeroI32, Instruction::I32DivSImm16, execute_i32_div_s_imm16, ::i32_div_s), + (NonZeroI32, Instruction::I32RemSImm16, execute_i32_rem_s_imm16, ::i32_rem_s), + + (NonZeroI64, Instruction::I64DivSImm16, execute_i64_div_s_imm16, ::i64_div_s), + (NonZeroI64, Instruction::I64RemSImm16, execute_i64_rem_s_imm16, ::i64_rem_s), + } +} + +macro_rules! impl_divrem_u_imm16 { + ( $( ($ty:ty, Instruction::$var_name:ident, $fn_name:ident, $op:expr) ),* $(,)? ) => { + $( + #[doc = concat!("Executes an [`Instruction::", stringify!($var_name), "`].")] + #[inline(always)] + pub fn $fn_name(&mut self, instr: BinInstrImm16<$ty>) { + self.execute_divrem_imm16(instr, $op) + } + )* + }; +} +impl<'ctx, 'engine> Executor<'ctx, 'engine> { + impl_divrem_u_imm16! { + (NonZeroU32, Instruction::I32DivUImm16, execute_i32_div_u_imm16, ::i32_div_u), + (NonZeroU32, Instruction::I32RemUImm16, execute_i32_rem_u_imm16, ::i32_rem_u), + + (NonZeroU64, Instruction::I64DivUImm16, execute_i64_div_u_imm16, ::i64_div_u), + (NonZeroU64, Instruction::I64RemUImm16, execute_i64_rem_u_imm16, ::i64_rem_u), } } diff --git a/crates/wasmi/src/engine/translator/mod.rs b/crates/wasmi/src/engine/translator/mod.rs index 1b329112fb..5e24f43b45 100644 --- a/crates/wasmi/src/engine/translator/mod.rs +++ b/crates/wasmi/src/engine/translator/mod.rs @@ -1469,10 +1469,14 @@ impl<'parser> FuncTranslator<'parser> { /// /// - `{i32, i64}.{div_u, div_s, rem_u, rem_s}` #[allow(clippy::too_many_arguments)] - pub fn translate_divrem( + pub fn translate_divrem( &mut self, make_instr: fn(result: Register, lhs: Register, rhs: Register) -> Instruction, - make_instr_imm16: fn(result: Register, lhs: Register, rhs: Const16) -> Instruction, + make_instr_imm16: fn( + result: Register, + lhs: Register, + rhs: Const16, + ) -> Instruction, make_instr_imm16_rev: fn(result: Register, lhs: Const16, rhs: Register) -> Instruction, consteval: fn(TypedValue, TypedValue) -> Result, make_instr_opt: fn( @@ -1488,6 +1492,7 @@ impl<'parser> FuncTranslator<'parser> { ) -> Result<(), TranslationError> where T: WasmInteger, + NonZeroT: Copy + TryFrom + TryInto>, { bail_unreachable!(self); match self.alloc.stack.pop2() { @@ -1499,16 +1504,16 @@ impl<'parser> FuncTranslator<'parser> { self.push_binary_instr(lhs, rhs, make_instr) } (TypedProvider::Register(lhs), TypedProvider::Const(rhs)) => { - if T::from(rhs).eq_zero() { + let Some(non_zero_rhs) = NonZeroT::try_from(T::from(rhs)).ok() else { // Optimization: division by zero always traps self.translate_trap(TrapCode::IntegerDivisionByZero)?; return Ok(()); - } + }; if make_instr_reg_imm_opt(self, lhs, T::from(rhs))? { // Custom optimization was applied: return early return Ok(()); } - if self.try_push_binary_instr_imm16(lhs, T::from(rhs), make_instr_imm16)? { + if self.try_push_binary_instr_imm16(lhs, non_zero_rhs, make_instr_imm16)? { // Optimization was applied: return early. return Ok(()); } diff --git a/crates/wasmi/src/engine/translator/relink_result.rs b/crates/wasmi/src/engine/translator/relink_result.rs index 871552b602..c3a2f32a82 100644 --- a/crates/wasmi/src/engine/translator/relink_result.rs +++ b/crates/wasmi/src/engine/translator/relink_result.rs @@ -444,10 +444,10 @@ impl Instruction { I::I32AddImm16(instr) | I::I32SubImm16(instr) | I::I32SubImm16Rev(instr) | - I::I32MulImm16(instr) | - I::I32DivSImm16(instr) | - I::I32DivSImm16Rev(instr) | - I::I32RemSImm16(instr) | + I::I32MulImm16(instr) => relink_simple(instr, new_result, old_result), + I::I32DivSImm16(instr) => relink_simple(instr, new_result, old_result), + I::I32DivSImm16Rev(instr) => relink_simple(instr, new_result, old_result), + I::I32RemSImm16(instr) => relink_simple(instr, new_result, old_result), I::I32RemSImm16Rev(instr) | I::I32AndEqzImm16(instr) | I::I32AndImm16(instr) | @@ -465,18 +465,18 @@ impl Instruction { I::I32RotlImm16Rev(instr) | I::I32RotrImm(instr) | I::I32RotrImm16Rev(instr) => relink_simple(instr, new_result, old_result), - I::I32DivUImm16(instr) | - I::I32DivUImm16Rev(instr) | - I::I32RemUImm16(instr) | + I::I32DivUImm16(instr) => relink_simple(instr, new_result, old_result), + I::I32DivUImm16Rev(instr) => relink_simple(instr, new_result, old_result), + I::I32RemUImm16(instr) => relink_simple(instr, new_result, old_result), I::I32RemUImm16Rev(instr) => relink_simple(instr, new_result, old_result), I::I64AddImm16(instr) | I::I64SubImm16(instr) | I::I64SubImm16Rev(instr) | - I::I64MulImm16(instr) | - I::I64DivSImm16(instr) | - I::I64DivSImm16Rev(instr) | - I::I64RemSImm16(instr) | + I::I64MulImm16(instr) => relink_simple(instr, new_result, old_result), + I::I64DivSImm16(instr) => relink_simple(instr, new_result, old_result), + I::I64DivSImm16Rev(instr) => relink_simple(instr, new_result, old_result), + I::I64RemSImm16(instr) => relink_simple(instr, new_result, old_result), I::I64RemSImm16Rev(instr) | I::I64AndImm16(instr) | I::I64OrImm16(instr) | @@ -491,9 +491,9 @@ impl Instruction { I::I64RotlImm16Rev(instr) | I::I64RotrImm(instr) | I::I64RotrImm16Rev(instr) => relink_simple(instr, new_result, old_result), - I::I64DivUImm16(instr) | - I::I64DivUImm16Rev(instr) | - I::I64RemUImm16(instr) | + I::I64DivUImm16(instr) => relink_simple(instr, new_result, old_result), + I::I64DivUImm16Rev(instr) => relink_simple(instr, new_result, old_result), + I::I64RemUImm16(instr) => relink_simple(instr, new_result, old_result), I::I64RemUImm16Rev(instr) => relink_simple(instr, new_result, old_result), I::I32WrapI64(instr) | diff --git a/crates/wasmi/src/engine/translator/tests/display_wasm.rs b/crates/wasmi/src/engine/translator/tests/display_wasm.rs index 3ad1137b5f..af0633ca2c 100644 --- a/crates/wasmi/src/engine/translator/tests/display_wasm.rs +++ b/crates/wasmi/src/engine/translator/tests/display_wasm.rs @@ -1,5 +1,9 @@ use crate::{core::ValueType, Value}; -use core::{fmt, fmt::Display}; +use core::{ + fmt, + fmt::Display, + num::{NonZeroI32, NonZeroI64, NonZeroU32, NonZeroU64}, +}; /// [`Display`] wrapper for a value `T` where `T` is a Wasm type. pub struct DisplayWasm(T); @@ -11,8 +15,8 @@ impl From for DisplayWasm { } macro_rules! impl_display_for_int { - ( $float_ty:ty ) => { - impl Display for DisplayWasm<$float_ty> { + ( $int_ty:ty ) => { + impl Display for DisplayWasm<$int_ty> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } @@ -28,6 +32,20 @@ impl_display_for_int!(u32); impl_display_for_int!(i64); impl_display_for_int!(u64); +macro_rules! impl_display_for_nonzero_int { + ( $nonzero_int:ty ) => { + impl Display for DisplayWasm<$nonzero_int> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0.get()) + } + } + }; +} +impl_display_for_nonzero_int!(NonZeroI32); +impl_display_for_nonzero_int!(NonZeroI64); +impl_display_for_nonzero_int!(NonZeroU32); +impl_display_for_nonzero_int!(NonZeroU64); + macro_rules! impl_display_for_float { ( $float_ty:ty ) => { impl Display for DisplayWasm<$float_ty> { diff --git a/crates/wasmi/src/engine/translator/tests/op/binary/i32_div_s.rs b/crates/wasmi/src/engine/translator/tests/op/binary/i32_div_s.rs index a395c1f5e8..c3c2be061b 100644 --- a/crates/wasmi/src/engine/translator/tests/op/binary/i32_div_s.rs +++ b/crates/wasmi/src/engine/translator/tests/op/binary/i32_div_s.rs @@ -27,7 +27,7 @@ fn reg_reg() { #[test] #[cfg_attr(miri, ignore)] fn reg_imm16() { - test_binary_reg_imm16::(WASM_OP, 100, Instruction::i32_div_s_imm16) + test_binary_reg_imm16::(WASM_OP, nonzero_i32(100), Instruction::i32_div_s_imm16) } #[test] diff --git a/crates/wasmi/src/engine/translator/tests/op/binary/i32_div_u.rs b/crates/wasmi/src/engine/translator/tests/op/binary/i32_div_u.rs index 430df67311..9b8b70fd8d 100644 --- a/crates/wasmi/src/engine/translator/tests/op/binary/i32_div_u.rs +++ b/crates/wasmi/src/engine/translator/tests/op/binary/i32_div_u.rs @@ -27,7 +27,7 @@ fn reg_reg() { #[test] #[cfg_attr(miri, ignore)] fn reg_imm16() { - test_binary_reg_imm16::(WASM_OP, 100, Instruction::i32_div_u_imm16) + test_binary_reg_imm16::(WASM_OP, nonzero_u32(100), Instruction::i32_div_u_imm16) } #[test] diff --git a/crates/wasmi/src/engine/translator/tests/op/binary/i32_rem_s.rs b/crates/wasmi/src/engine/translator/tests/op/binary/i32_rem_s.rs index e46b21b909..e6ec27f4ff 100644 --- a/crates/wasmi/src/engine/translator/tests/op/binary/i32_rem_s.rs +++ b/crates/wasmi/src/engine/translator/tests/op/binary/i32_rem_s.rs @@ -27,7 +27,7 @@ fn reg_reg() { #[test] #[cfg_attr(miri, ignore)] fn reg_imm16() { - test_binary_reg_imm16::(WASM_OP, 100, Instruction::i32_rem_s_imm16) + test_binary_reg_imm16::(WASM_OP, nonzero_i32(100), Instruction::i32_rem_s_imm16) } #[test] diff --git a/crates/wasmi/src/engine/translator/tests/op/binary/i32_rem_u.rs b/crates/wasmi/src/engine/translator/tests/op/binary/i32_rem_u.rs index bfdc8bf9c4..7b982c1238 100644 --- a/crates/wasmi/src/engine/translator/tests/op/binary/i32_rem_u.rs +++ b/crates/wasmi/src/engine/translator/tests/op/binary/i32_rem_u.rs @@ -27,7 +27,7 @@ fn reg_reg() { #[test] #[cfg_attr(miri, ignore)] fn reg_imm16() { - test_binary_reg_imm16::(WASM_OP, 100, Instruction::i32_rem_u_imm16) + test_binary_reg_imm16::(WASM_OP, nonzero_u32(100), Instruction::i32_rem_u_imm16) } #[test] diff --git a/crates/wasmi/src/engine/translator/tests/op/binary/i64_div_s.rs b/crates/wasmi/src/engine/translator/tests/op/binary/i64_div_s.rs index ba14d52894..4376a66b1f 100644 --- a/crates/wasmi/src/engine/translator/tests/op/binary/i64_div_s.rs +++ b/crates/wasmi/src/engine/translator/tests/op/binary/i64_div_s.rs @@ -27,7 +27,7 @@ fn reg_reg() { #[test] #[cfg_attr(miri, ignore)] fn reg_imm16() { - test_binary_reg_imm16::(WASM_OP, 100, Instruction::i64_div_s_imm16) + test_binary_reg_imm16::(WASM_OP, nonzero_i64(100), Instruction::i64_div_s_imm16) } #[test] diff --git a/crates/wasmi/src/engine/translator/tests/op/binary/i64_div_u.rs b/crates/wasmi/src/engine/translator/tests/op/binary/i64_div_u.rs index 7a95624247..52c3b59d62 100644 --- a/crates/wasmi/src/engine/translator/tests/op/binary/i64_div_u.rs +++ b/crates/wasmi/src/engine/translator/tests/op/binary/i64_div_u.rs @@ -27,7 +27,7 @@ fn reg_reg() { #[test] #[cfg_attr(miri, ignore)] fn reg_imm16() { - test_binary_reg_imm16::(WASM_OP, 100, Instruction::i64_div_u_imm16) + test_binary_reg_imm16::(WASM_OP, nonzero_u64(100), Instruction::i64_div_u_imm16) } #[test] diff --git a/crates/wasmi/src/engine/translator/tests/op/binary/i64_rem_s.rs b/crates/wasmi/src/engine/translator/tests/op/binary/i64_rem_s.rs index 93a330fe6b..6d5888cce4 100644 --- a/crates/wasmi/src/engine/translator/tests/op/binary/i64_rem_s.rs +++ b/crates/wasmi/src/engine/translator/tests/op/binary/i64_rem_s.rs @@ -27,7 +27,7 @@ fn reg_reg() { #[test] #[cfg_attr(miri, ignore)] fn reg_imm16() { - test_binary_reg_imm16::(WASM_OP, 100, Instruction::i64_rem_s_imm16) + test_binary_reg_imm16::(WASM_OP, nonzero_i64(100), Instruction::i64_rem_s_imm16) } #[test] diff --git a/crates/wasmi/src/engine/translator/tests/op/binary/i64_rem_u.rs b/crates/wasmi/src/engine/translator/tests/op/binary/i64_rem_u.rs index 0768ecaaee..53a80db9e1 100644 --- a/crates/wasmi/src/engine/translator/tests/op/binary/i64_rem_u.rs +++ b/crates/wasmi/src/engine/translator/tests/op/binary/i64_rem_u.rs @@ -1,4 +1,5 @@ use super::*; +use core::num::NonZeroU64; use wasmi_core::TrapCode; const WASM_OP: WasmOp = WasmOp::binary(WasmType::I64, "rem_u"); @@ -27,7 +28,7 @@ fn reg_reg() { #[test] #[cfg_attr(miri, ignore)] fn reg_imm16() { - test_binary_reg_imm16::(WASM_OP, 100, Instruction::i64_rem_u_imm16) + test_binary_reg_imm16::(WASM_OP, nonzero_u64(100), Instruction::i64_rem_u_imm16) } #[test] diff --git a/crates/wasmi/src/engine/translator/tests/op/binary/mod.rs b/crates/wasmi/src/engine/translator/tests/op/binary/mod.rs index 7d61f906a9..c7e3f49994 100644 --- a/crates/wasmi/src/engine/translator/tests/op/binary/mod.rs +++ b/crates/wasmi/src/engine/translator/tests/op/binary/mod.rs @@ -5,6 +5,7 @@ //! These tests include Wasm arithmetic, logical, bitwise, shift and rotate instructions. use super::*; +use core::num::{NonZeroI32, NonZeroI64, NonZeroU32, NonZeroU64}; mod f32_add; mod f32_copysign; @@ -51,6 +52,26 @@ mod i64_shr_u; mod i64_sub; mod i64_xor; +/// Helper to create a [`NonZeroI32`]. +fn nonzero_i32(value: i32) -> NonZeroI32 { + NonZeroI32::new(value).unwrap() +} + +/// Helper to create a [`NonZeroU32`]. +fn nonzero_u32(value: u32) -> NonZeroU32 { + NonZeroU32::new(value).unwrap() +} + +/// Helper to create a [`NonZeroI64`]. +fn nonzero_i64(value: i64) -> NonZeroI64 { + NonZeroI64::new(value).unwrap() +} + +/// Helper to create a [`NonZeroU64`]. +fn nonzero_u64(value: u64) -> NonZeroU64 { + NonZeroU64::new(value).unwrap() +} + /// Creates an [`Instruction::ReturnF64Imm32`] from the given `f64` value. /// /// # Panics diff --git a/crates/wasmi/src/engine/translator/visit.rs b/crates/wasmi/src/engine/translator/visit.rs index 882bd1a048..563acc09e7 100644 --- a/crates/wasmi/src/engine/translator/visit.rs +++ b/crates/wasmi/src/engine/translator/visit.rs @@ -29,6 +29,7 @@ use crate::{ Mutability, }; use alloc::collections::BTreeMap; +use core::num::{NonZeroU32, NonZeroU64}; use wasmi_core::{TrapCode, ValueType, F32, F64}; use wasmparser::VisitOperator; @@ -2245,7 +2246,7 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> { } fn visit_i32_div_u(&mut self) -> Self::Output { - self.translate_divrem::( + self.translate_divrem::( Instruction::i32_div_u, Instruction::i32_div_u_imm16, Instruction::i32_div_u_imm16_rev, @@ -2281,7 +2282,7 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> { } fn visit_i32_rem_u(&mut self) -> Self::Output { - self.translate_divrem::( + self.translate_divrem::( Instruction::i32_rem_u, Instruction::i32_rem_u_imm16, Instruction::i32_rem_u_imm16_rev, @@ -2553,7 +2554,7 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> { } fn visit_i64_div_u(&mut self) -> Self::Output { - self.translate_divrem::( + self.translate_divrem::( Instruction::i64_div_u, Instruction::i64_div_u_imm16, Instruction::i64_div_u_imm16_rev, @@ -2589,7 +2590,7 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> { } fn visit_i64_rem_u(&mut self) -> Self::Output { - self.translate_divrem::( + self.translate_divrem::( Instruction::i64_rem_u, Instruction::i64_rem_u_imm16, Instruction::i64_rem_u_imm16_rev, From 5d4ea7b24648def2ef5b7be188f428a1546b45a2 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Sun, 3 Dec 2023 15:54:00 +0100 Subject: [PATCH 6/7] Introduce `WasmTranslator` trait (#828) * create new WasmTranslator trait * make ValidatingFuncTranslator::current_pos private * impl WasmTranslator for ValidatingFuncTranslator * rename binding --- crates/wasmi/src/engine/mod.rs | 2 +- crates/wasmi/src/engine/translator/mod.rs | 139 ++++++++++++++++------ crates/wasmi/src/module/compile/mod.rs | 19 ++- crates/wasmi/src/module/mod.rs | 1 - crates/wasmi/src/module/parser.rs | 29 +---- 5 files changed, 119 insertions(+), 71 deletions(-) diff --git a/crates/wasmi/src/engine/mod.rs b/crates/wasmi/src/engine/mod.rs index 299f12e438..7aea15bf6a 100644 --- a/crates/wasmi/src/engine/mod.rs +++ b/crates/wasmi/src/engine/mod.rs @@ -38,7 +38,7 @@ pub(crate) use self::{ executor::Stack, func_args::{FuncFinished, FuncParams, FuncResults}, func_types::DedupFuncType, - translator::FuncTranslatorAllocations, + translator::{FuncTranslatorAllocations, ReusableAllocations, WasmTranslator}, }; use crate::{core::Trap, Func, FuncType, StoreContextMut}; use alloc::{sync::Arc, vec::Vec}; diff --git a/crates/wasmi/src/engine/translator/mod.rs b/crates/wasmi/src/engine/translator/mod.rs index 5e24f43b45..4d3f732607 100644 --- a/crates/wasmi/src/engine/translator/mod.rs +++ b/crates/wasmi/src/engine/translator/mod.rs @@ -52,13 +52,13 @@ use crate::{ config::FuelCosts, CompiledFunc, }, - module::{BlockType, FuncIdx, FuncTypeIdx, ModuleResources, ReusableAllocations}, + module::{BlockType, FuncIdx, FuncTypeIdx, ModuleResources}, Engine, FuncType, }; use alloc::vec::Vec; use wasmi_core::{TrapCode, UntypedValue, ValueType}; -use wasmparser::{BinaryReaderError, MemArg, VisitOperator}; +use wasmparser::{BinaryReaderError, FuncValidatorAllocations, MemArg, VisitOperator}; /// Reusable allocations of a [`FuncTranslator`]. #[derive(Debug, Default)] @@ -99,6 +99,72 @@ pub struct ValidatingFuncTranslator<'parser> { translator: FuncTranslator<'parser>, } +/// Reusable heap allocations for function validation and translation. +pub struct ReusableAllocations { + pub translation: FuncTranslatorAllocations, + pub validation: FuncValidatorAllocations, +} + +impl Default for ReusableAllocations { + fn default() -> Self { + let translation = FuncTranslatorAllocations::default(); + let validation = FuncValidatorAllocations::default(); + Self { + translation, + validation, + } + } +} + +/// A WebAssembly (Wasm) function translator. +pub trait WasmTranslator<'parser>: VisitOperator<'parser> { + /// The reusable allocations required by the [`WasmTranslator`]. + /// + /// # Note + /// + /// Those allocations can be cached on the caller side for reusability + /// in order to avoid frequent memory allocations and deallocations. + type Allocations: Default; + + /// Translates the given local variables for the translated function. + fn translate_locals( + &mut self, + amount: u32, + value_type: wasmparser::ValType, + ) -> Result<(), TranslationError>; + + /// Informs the [`WasmTranslator`] that the Wasm function header translation is finished. + /// + /// # Note + /// + /// - After this function call no more locals and parameters may be registered + /// to the [`WasmTranslator`] via [`WasmTranslator::translate_locals`]. + /// - After this function call the [`WasmTranslator`] expects its [`VisitOperator`] + /// trait methods to be called for translating the Wasm operators of the + /// translated function. + /// + /// # Dev. Note + /// + /// This got introduced to properly calculate the fuel costs for all local variables + /// and function parameters. + fn finish_translate_locals(&mut self) -> Result<(), TranslationError>; + + /// Updates the [`WasmTranslator`] about the current byte position within translated Wasm binary. + /// + /// # Note + /// + /// This information is mainly required for properly locating translation errors. + fn update_pos(&mut self, pos: usize); + + /// Finishes constructing the Wasm function translation. + /// + /// # Note + /// + /// - Initialized the [`CompiledFunc`] in the [`Engine`]. + /// - Returns the allocations used for translation. + fn finish(self) -> Result; +} + impl<'parser> ValidatingFuncTranslator<'parser> { /// Creates a new [`ValidatingFuncTranslator`]. pub fn new( @@ -116,43 +182,53 @@ impl<'parser> ValidatingFuncTranslator<'parser> { }) } - /// Translates the given local variables for the translated function. - pub fn translate_locals( + /// Returns the current position within the Wasm binary while parsing operators. + fn current_pos(&self) -> usize { + self.pos + } + + /// Translates into `wasmi` bytecode if the current code path is reachable. + fn validate_then_translate( + &mut self, + validate: V, + translate: T, + ) -> Result<(), TranslationError> + where + V: FnOnce(&mut FuncValidator) -> Result<(), BinaryReaderError>, + T: FnOnce(&mut FuncTranslator<'parser>) -> Result<(), TranslationError>, + { + validate(&mut self.validator)?; + translate(&mut self.translator)?; + Ok(()) + } +} + +impl<'parser> WasmTranslator<'parser> for ValidatingFuncTranslator<'parser> { + type Allocations = ReusableAllocations; + + fn translate_locals( &mut self, - offset: usize, amount: u32, value_type: wasmparser::ValType, ) -> Result<(), TranslationError> { - self.validator.define_locals(offset, amount, value_type)?; + self.validator + .define_locals(self.current_pos(), amount, value_type)?; self.translator.register_locals(amount)?; Ok(()) } - /// This informs the [`ValidatingFuncTranslator`] that the function header translation is finished. - /// - /// # Note - /// - /// This was introduced to properly calculate the fuel costs for all local variables - /// and function parameters. After this function call no more locals and parameters may - /// be added to this function translation. - pub fn finish_translate_locals(&mut self) -> Result<(), TranslationError> { + fn finish_translate_locals(&mut self) -> Result<(), TranslationError> { self.translator.finish_translate_locals()?; Ok(()) } - /// Updates the current position within the Wasm binary while parsing operators. - pub fn update_pos(&mut self, pos: usize) { + fn update_pos(&mut self, pos: usize) { self.pos = pos; } - /// Returns the current position within the Wasm binary while parsing operators. - pub fn current_pos(&self) -> usize { - self.pos - } - - /// Finishes constructing the function by initializing its [`CompiledFunc`]. - pub fn finish(mut self, offset: usize) -> Result { - self.validator.finish(offset)?; + fn finish(mut self) -> Result { + let pos = self.current_pos(); + self.validator.finish(pos)?; self.translator.finish()?; let translation = self.translator.into_allocations(); let validation = self.validator.into_allocations(); @@ -162,21 +238,6 @@ impl<'parser> ValidatingFuncTranslator<'parser> { }; Ok(allocations) } - - /// Translates into `wasmi` bytecode if the current code path is reachable. - fn validate_then_translate( - &mut self, - validate: V, - translate: T, - ) -> Result<(), TranslationError> - where - V: FnOnce(&mut FuncValidator) -> Result<(), BinaryReaderError>, - T: FnOnce(&mut FuncTranslator<'parser>) -> Result<(), TranslationError>, - { - validate(&mut self.validator)?; - translate(&mut self.translator)?; - Ok(()) - } } macro_rules! impl_visit_operator { diff --git a/crates/wasmi/src/module/compile/mod.rs b/crates/wasmi/src/module/compile/mod.rs index ee4500f77d..499f306f24 100644 --- a/crates/wasmi/src/module/compile/mod.rs +++ b/crates/wasmi/src/module/compile/mod.rs @@ -1,7 +1,13 @@ pub use self::block_type::BlockType; -use super::{parser::ReusableAllocations, FuncIdx, ModuleResources}; +use super::{FuncIdx, ModuleResources}; use crate::{ - engine::{CompiledFunc, FuncTranslatorAllocations, ValidatingFuncTranslator}, + engine::{ + CompiledFunc, + FuncTranslatorAllocations, + ReusableAllocations, + ValidatingFuncTranslator, + WasmTranslator, + }, errors::ModuleError, }; use wasmparser::{FuncValidator, FunctionBody, ValidatorResources}; @@ -68,8 +74,9 @@ impl<'parser> FunctionTranslator<'parser> { } /// Finishes construction of the function and returns its [`CompiledFunc`]. - fn finish(self, offset: usize) -> Result { - self.func_builder.finish(offset).map_err(Into::into) + fn finish(mut self, offset: usize) -> Result { + self.func_builder.update_pos(offset); + self.func_builder.finish().map_err(Into::into) } /// Translates local variables of the Wasm function. @@ -79,8 +86,8 @@ impl<'parser> FunctionTranslator<'parser> { for _ in 0..len_locals { let offset = reader.original_position(); let (amount, value_type) = reader.read()?; - self.func_builder - .translate_locals(offset, amount, value_type)?; + self.func_builder.update_pos(offset); + self.func_builder.translate_locals(amount, value_type)?; } self.func_builder.finish_translate_locals()?; Ok(()) diff --git a/crates/wasmi/src/module/mod.rs b/crates/wasmi/src/module/mod.rs index d2691f7217..6c185ad692 100644 --- a/crates/wasmi/src/module/mod.rs +++ b/crates/wasmi/src/module/mod.rs @@ -28,7 +28,6 @@ pub use self::{ global::GlobalIdx, import::{FuncTypeIdx, ImportName}, instantiate::{InstancePre, InstantiationError}, - parser::ReusableAllocations, read::Read, }; pub(crate) use self::{ diff --git a/crates/wasmi/src/module/parser.rs b/crates/wasmi/src/module/parser.rs index 7f114d79c9..96a37d4e3b 100644 --- a/crates/wasmi/src/module/parser.rs +++ b/crates/wasmi/src/module/parser.rs @@ -13,7 +13,7 @@ use super::{ Read, }; use crate::{ - engine::{CompiledFunc, FuncTranslatorAllocations}, + engine::{CompiledFunc, ReusableAllocations}, Engine, FuncType, MemoryType, @@ -27,7 +27,6 @@ use wasmparser::{ ElementSectionReader, Encoding, ExportSectionReader, - FuncValidatorAllocations, FunctionBody, FunctionSectionReader, GlobalSectionReader, @@ -67,24 +66,6 @@ pub struct ModuleParser<'engine> { allocations: ReusableAllocations, } -/// Reusable heap allocations for function validation and translation. -pub struct ReusableAllocations { - pub translation: FuncTranslatorAllocations, - pub validation: FuncValidatorAllocations, -} - -impl ReusableAllocations { - /// Creates new [`ReusableAllocations`] for the given [`Engine`]. - pub fn new() -> Self { - let translation = FuncTranslatorAllocations::default(); - let validation = FuncValidatorAllocations::default(); - Self { - translation, - validation, - } - } -} - impl<'engine> ModuleParser<'engine> { /// Creates a new [`ModuleParser`] for the given [`Engine`]. fn new(engine: &'engine Engine) -> Self { @@ -96,7 +77,7 @@ impl<'engine> ModuleParser<'engine> { validator, parser, compiled_funcs: 0, - allocations: ReusableAllocations::new(), + allocations: ReusableAllocations::default(), } } @@ -509,14 +490,14 @@ impl<'engine> ModuleParser<'engine> { /// /// If the function body fails to validate. fn process_code_entry(&mut self, func_body: FunctionBody) -> Result<(), ModuleError> { - let (func, compiled_func_2) = self.next_func(); + let (func, compiled_func) = self.next_func(); let validator = self.validator.code_section_entry(&func_body)?; let module_resources = ModuleResources::new(&self.builder); - let dummy_allocations = ReusableAllocations::new(); + let dummy_allocations = ReusableAllocations::default(); let allocations = replace(&mut self.allocations, dummy_allocations); let allocations = translate( func, - compiled_func_2, + compiled_func, func_body, validator.into_validator(allocations.validation), module_resources, From 1ce23700e1cdf46d8c770bb7e46955b277311847 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Sun, 3 Dec 2023 18:54:55 +0100 Subject: [PATCH 7/7] Implement `Module::new_unchecked` (#829) * use derive(Default) for ReusableAllocations * use Self::Allocations * make many FuncTranslator methods private None of them needed to be public. This was a simple oversight. * impl WasmTranslator for FuncTranslator * rename parameter * remove unnecessary map_err(Into::into) in macro * implement Module::new_unchecked * apply rustfmt * add translation benchmarks for Module::new_unchecked --- crates/wasmi/benches/benches.rs | 55 +++++-- crates/wasmi/src/engine/mod.rs | 10 +- crates/wasmi/src/engine/translator/mod.rs | 168 ++++++++++------------ crates/wasmi/src/module/compile/mod.rs | 79 +++++++--- crates/wasmi/src/module/mod.rs | 41 +++++- crates/wasmi/src/module/parser.rs | 108 +++++++++++--- 6 files changed, 311 insertions(+), 150 deletions(-) diff --git a/crates/wasmi/benches/benches.rs b/crates/wasmi/benches/benches.rs index dc2a02ebc0..f64e6f2f98 100644 --- a/crates/wasmi/benches/benches.rs +++ b/crates/wasmi/benches/benches.rs @@ -98,56 +98,85 @@ enum FuelMetering { Disabled, } -fn bench_translate_for(c: &mut Criterion, name: &str, path: &str, fuel_metering: FuelMetering) { +/// How to translate a Wasm module. +enum Validation { + /// Uses [`Module::new`]. + Checked, + /// Uses [`Module::new_unchecked`]. + Unchecked, +} + +fn bench_translate_for( + c: &mut Criterion, + name: &str, + path: &str, + validation: Validation, + fuel_metering: FuelMetering, +) { let fuel_id = match fuel_metering { FuelMetering::Enabled => "fuel", FuelMetering::Disabled => "default", }; - let bench_id = format!("translate/{name}/{fuel_id}"); + let safety = match validation { + Validation::Checked => "checked", + Validation::Unchecked => "unchecked", + }; + let bench_id = format!("translate/{name}/{safety}/{fuel_id}"); let mut config = bench_config(); if matches!(fuel_metering, FuelMetering::Enabled) { config.consume_fuel(true); } + let create_module = match validation { + Validation::Checked => { + |engine: &Engine, bytes: &[u8]| -> Module { Module::new(&engine, &bytes[..]).unwrap() } + } + Validation::Unchecked => |engine: &Engine, bytes: &[u8]| -> Module { + // Safety: We made sure that all translation benchmark inputs are valid Wasm. + unsafe { Module::new_unchecked(engine, &bytes[..]).unwrap() } + }, + }; c.bench_function(&bench_id, |b| { let wasm_bytes = load_wasm_from_file(path); b.iter(|| { let engine = Engine::new(&config); - let _module = Module::new(&engine, &wasm_bytes[..]).unwrap(); + _ = create_module(&engine, &wasm_bytes[..]); }) }); } -fn bench_translate_for_both(c: &mut Criterion, name: &str, path: &str) { - bench_translate_for(c, name, path, FuelMetering::Disabled); - bench_translate_for(c, name, path, FuelMetering::Enabled); +fn bench_translate_for_all(c: &mut Criterion, name: &str, path: &str) { + bench_translate_for(c, name, path, Validation::Checked, FuelMetering::Disabled); + bench_translate_for(c, name, path, Validation::Checked, FuelMetering::Enabled); + bench_translate_for(c, name, path, Validation::Unchecked, FuelMetering::Disabled); + bench_translate_for(c, name, path, Validation::Unchecked, FuelMetering::Enabled); } fn bench_translate_wasm_kernel(c: &mut Criterion) { - bench_translate_for_both(c, "wasm_kernel", WASM_KERNEL); + bench_translate_for_all(c, "wasm_kernel", WASM_KERNEL); } fn bench_translate_spidermonkey(c: &mut Criterion) { - bench_translate_for_both(c, "spidermonkey", "benches/wasm/spidermonkey.wasm"); + bench_translate_for_all(c, "spidermonkey", "benches/wasm/spidermonkey.wasm"); } fn bench_translate_bz2(c: &mut Criterion) { - bench_translate_for_both(c, "bz2", "benches/wasm/bz2.wasm"); + bench_translate_for_all(c, "bz2", "benches/wasm/bz2.wasm"); } fn bench_translate_pulldown_cmark(c: &mut Criterion) { - bench_translate_for_both(c, "pulldown_cmark", "benches/wasm/pulldown-cmark.wasm"); + bench_translate_for_all(c, "pulldown_cmark", "benches/wasm/pulldown-cmark.wasm"); } fn bench_translate_erc20(c: &mut Criterion) { - bench_translate_for_both(c, "erc20", "benches/wasm/erc20.wasm"); + bench_translate_for_all(c, "erc20", "benches/wasm/erc20.wasm"); } fn bench_translate_erc721(c: &mut Criterion) { - bench_translate_for_both(c, "erc721", "benches/wasm/erc721.wasm"); + bench_translate_for_all(c, "erc721", "benches/wasm/erc721.wasm"); } fn bench_translate_erc1155(c: &mut Criterion) { - bench_translate_for_both(c, "erc1155", "benches/wasm/erc1155.wasm"); + bench_translate_for_all(c, "erc1155", "benches/wasm/erc1155.wasm"); } fn bench_instantiate_wasm_kernel(c: &mut Criterion) { diff --git a/crates/wasmi/src/engine/mod.rs b/crates/wasmi/src/engine/mod.rs index 7aea15bf6a..e9139bae84 100644 --- a/crates/wasmi/src/engine/mod.rs +++ b/crates/wasmi/src/engine/mod.rs @@ -31,14 +31,20 @@ pub use self::{ limits::StackLimits, resumable::{ResumableCall, ResumableInvocation, TypedResumableCall, TypedResumableInvocation}, traits::{CallParams, CallResults}, - translator::{Instr, TranslationError, ValidatingFuncTranslator}, + translator::{Instr, TranslationError}, }; pub(crate) use self::{ config::FuelCosts, executor::Stack, func_args::{FuncFinished, FuncParams, FuncResults}, func_types::DedupFuncType, - translator::{FuncTranslatorAllocations, ReusableAllocations, WasmTranslator}, + translator::{ + FuncTranslator, + FuncTranslatorAllocations, + ReusableAllocations, + ValidatingFuncTranslator, + WasmTranslator, + }, }; use crate::{core::Trap, Func, FuncType, StoreContextMut}; use alloc::{sync::Arc, vec::Vec}; diff --git a/crates/wasmi/src/engine/translator/mod.rs b/crates/wasmi/src/engine/translator/mod.rs index 4d3f732607..7c759dbe55 100644 --- a/crates/wasmi/src/engine/translator/mod.rs +++ b/crates/wasmi/src/engine/translator/mod.rs @@ -100,24 +100,16 @@ pub struct ValidatingFuncTranslator<'parser> { } /// Reusable heap allocations for function validation and translation. +#[derive(Default)] pub struct ReusableAllocations { pub translation: FuncTranslatorAllocations, pub validation: FuncValidatorAllocations, } -impl Default for ReusableAllocations { - fn default() -> Self { - let translation = FuncTranslatorAllocations::default(); - let validation = FuncValidatorAllocations::default(); - Self { - translation, - validation, - } - } -} - /// A WebAssembly (Wasm) function translator. -pub trait WasmTranslator<'parser>: VisitOperator<'parser> { +pub trait WasmTranslator<'parser>: + VisitOperator<'parser, Output = Result<(), TranslationError>> +{ /// The reusable allocations required by the [`WasmTranslator`]. /// /// # Note @@ -162,7 +154,7 @@ pub trait WasmTranslator<'parser>: VisitOperator<'parser> { /// /// - Initialized the [`CompiledFunc`] in the [`Engine`]. /// - Returns the allocations used for translation. - fn finish(self) -> Result; + fn finish(self) -> Result; } impl<'parser> ValidatingFuncTranslator<'parser> { @@ -172,9 +164,9 @@ impl<'parser> ValidatingFuncTranslator<'parser> { compiled_func: CompiledFunc, res: ModuleResources<'parser>, validator: FuncValidator, - allocations: FuncTranslatorAllocations, + alloc: FuncTranslatorAllocations, ) -> Result { - let translator = FuncTranslator::new(func, compiled_func, res, allocations)?; + let translator = FuncTranslator::new(func, compiled_func, res, alloc)?; Ok(Self { pos: 0, validator, @@ -213,7 +205,7 @@ impl<'parser> WasmTranslator<'parser> for ValidatingFuncTranslator<'parser> { ) -> Result<(), TranslationError> { self.validator .define_locals(self.current_pos(), amount, value_type)?; - self.translator.register_locals(amount)?; + self.translator.translate_locals(amount, value_type)?; Ok(()) } @@ -226,11 +218,10 @@ impl<'parser> WasmTranslator<'parser> for ValidatingFuncTranslator<'parser> { self.pos = pos; } - fn finish(mut self) -> Result { + fn finish(mut self) -> Result { let pos = self.current_pos(); self.validator.finish(pos)?; - self.translator.finish()?; - let translation = self.translator.into_allocations(); + let translation = self.translator.finish()?; let validation = self.validator.into_allocations(); let allocations = ReusableAllocations { translation, @@ -240,6 +231,60 @@ impl<'parser> WasmTranslator<'parser> for ValidatingFuncTranslator<'parser> { } } +impl<'parser> WasmTranslator<'parser> for FuncTranslator<'parser> { + type Allocations = FuncTranslatorAllocations; + + fn translate_locals( + &mut self, + amount: u32, + _value_type: wasmparser::ValType, + ) -> Result<(), TranslationError> { + self.alloc.stack.register_locals(amount) + } + + fn finish_translate_locals(&mut self) -> Result<(), TranslationError> { + self.alloc.stack.finish_register_locals(); + Ok(()) + } + + fn update_pos(&mut self, _pos: usize) {} + + fn finish(mut self) -> Result { + self.alloc + .instr_encoder + .defrag_registers(&mut self.alloc.stack)?; + self.alloc.instr_encoder.update_branch_offsets()?; + let len_registers = self.alloc.stack.len_registers(); + if let Some(fuel_costs) = self.fuel_costs() { + // Note: Fuel metering is enabled so we need to bump the fuel + // of the function enclosing Wasm `block` by an amount + // that depends on the total number of registers used by + // the compiled function. + // Note: The function enclosing block fuel instruction is always + // the instruction at the 0th index if fuel metering is enabled. + let fuel_instr = Instr::from_u32(0); + let fuel_info = FuelInfo::some(*fuel_costs, fuel_instr); + self.alloc + .instr_encoder + .bump_fuel_consumption(fuel_info, |costs| { + costs.fuel_for_copies(u64::from(len_registers)) + })?; + } + let len_results = u16::try_from(self.func_type().results().len()) + .map_err(|_| TranslationError::new(TranslationErrorInner::TooManyFunctionResults))?; + let func_consts = self.alloc.stack.func_local_consts(); + let instrs = self.alloc.instr_encoder.drain_instrs(); + self.res.engine().init_func( + self.compiled_func, + len_registers, + len_results, + func_consts, + instrs, + ); + Ok(self.into_allocations()) + } +} + macro_rules! impl_visit_operator { ( @mvp BrTable { $arg:ident: $argty:ty } => $visit:ident $($rest:tt)* ) => { // We need to special case the `BrTable` operand since its @@ -249,7 +294,7 @@ macro_rules! impl_visit_operator { let offset = self.current_pos(); self.validate_then_translate( |validator| validator.visitor(offset).$visit($arg.clone()), - |translator| translator.$visit($arg.clone()).map_err(Into::into), + |translator| translator.$visit($arg.clone()), ) } impl_visit_operator!($($rest)*); @@ -277,7 +322,7 @@ macro_rules! impl_visit_operator { let offset = self.current_pos(); self.validate_then_translate( move |validator| validator.visitor(offset).$visit($($($arg),*)?), - move |translator| translator.$visit($($($arg),*)?).map_err(Into::into), + move |translator| translator.$visit($($($arg),*)?), ) } impl_visit_operator!($($rest)*); @@ -426,70 +471,13 @@ impl<'parser> FuncTranslator<'parser> { Ok(()) } - /// Registers an `amount` of local variables. - /// - /// # Panics - /// - /// If too many local variables have been registered. - pub fn register_locals(&mut self, amount: u32) -> Result<(), TranslationError> { - self.alloc.stack.register_locals(amount) - } - - /// This informs the [`FuncTranslator`] that the function header translation is finished. - /// - /// # Note - /// - /// This was introduced to properly calculate the fuel costs for all local variables - /// and function parameters. After this function call no more locals and parameters may - /// be added to this function translation. - pub fn finish_translate_locals(&mut self) -> Result<(), TranslationError> { - self.alloc.stack.finish_register_locals(); - Ok(()) - } - - /// Finishes constructing the function and returns its [`CompiledFunc`]. - pub fn finish(&mut self) -> Result<(), TranslationError> { - self.alloc - .instr_encoder - .defrag_registers(&mut self.alloc.stack)?; - self.alloc.instr_encoder.update_branch_offsets()?; - let len_registers = self.alloc.stack.len_registers(); - if let Some(fuel_costs) = self.fuel_costs() { - // Note: Fuel metering is enabled so we need to bump the fuel - // of the function enclosing Wasm `block` by an amount - // that depends on the total number of registers used by - // the compiled function. - // Note: The function enclosing block fuel instruction is always - // the instruction at the 0th index if fuel metering is enabled. - let fuel_instr = Instr::from_u32(0); - let fuel_info = FuelInfo::some(*fuel_costs, fuel_instr); - self.alloc - .instr_encoder - .bump_fuel_consumption(fuel_info, |costs| { - costs.fuel_for_copies(u64::from(len_registers)) - })?; - } - let len_results = u16::try_from(self.func_type().results().len()) - .map_err(|_| TranslationError::new(TranslationErrorInner::TooManyFunctionResults))?; - let func_consts = self.alloc.stack.func_local_consts(); - let instrs = self.alloc.instr_encoder.drain_instrs(); - self.res.engine().init_func( - self.compiled_func, - len_registers, - len_results, - func_consts, - instrs, - ); - Ok(()) - } - /// Returns a shared reference to the underlying [`Engine`]. fn engine(&self) -> &Engine { self.res.engine() } /// Consumes `self` and returns the underlying reusable [`FuncTranslatorAllocations`]. - pub fn into_allocations(self) -> FuncTranslatorAllocations { + fn into_allocations(self) -> FuncTranslatorAllocations { self.alloc } @@ -1530,7 +1518,7 @@ impl<'parser> FuncTranslator<'parser> { /// /// - `{i32, i64}.{div_u, div_s, rem_u, rem_s}` #[allow(clippy::too_many_arguments)] - pub fn translate_divrem( + fn translate_divrem( &mut self, make_instr: fn(result: Register, lhs: Register, rhs: Register) -> Instruction, make_instr_imm16: fn( @@ -1598,16 +1586,12 @@ impl<'parser> FuncTranslator<'parser> { } /// Can be used for [`Self::translate_binary`] (and variants) if no custom optimization shall be applied. - pub fn no_custom_opt( - &mut self, - _lhs: Lhs, - _rhs: Rhs, - ) -> Result { + fn no_custom_opt(&mut self, _lhs: Lhs, _rhs: Rhs) -> Result { Ok(false) } /// Translates a unary Wasm instruction to `wasmi` bytecode. - pub fn translate_unary( + fn translate_unary( &mut self, make_instr: fn(result: Register, input: Register) -> Instruction, consteval: fn(input: TypedValue) -> TypedValue, @@ -1627,7 +1611,7 @@ impl<'parser> FuncTranslator<'parser> { } /// Translates a fallible unary Wasm instruction to `wasmi` bytecode. - pub fn translate_unary_fallible( + fn translate_unary_fallible( &mut self, make_instr: fn(result: Register, input: Register) -> Instruction, consteval: fn(input: TypedValue) -> Result, @@ -1692,7 +1676,7 @@ impl<'parser> FuncTranslator<'parser> { /// - `{i32, i64, f32, f64}.load` /// - `i32.{load8_s, load8_u, load16_s, load16_u}` /// - `i64.{load8_s, load8_u, load16_s, load16_u load32_s, load32_u}` - pub fn translate_load( + fn translate_load( &mut self, memarg: MemArg, make_instr: fn(result: Register, ptr: Register) -> Instruction, @@ -2339,7 +2323,7 @@ impl<'parser> FuncTranslator<'parser> { } /// Translates a Wasm `reinterpret` instruction. - pub fn translate_reinterpret(&mut self, ty: ValueType) -> Result<(), TranslationError> { + fn translate_reinterpret(&mut self, ty: ValueType) -> Result<(), TranslationError> { bail_unreachable!(self); match self.alloc.stack.pop() { TypedProvider::Register(reg) => { @@ -2356,13 +2340,13 @@ impl<'parser> FuncTranslator<'parser> { } /// Translates an unconditional `return` instruction. - pub fn translate_return(&mut self) -> Result<(), TranslationError> { + fn translate_return(&mut self) -> Result<(), TranslationError> { let fuel_info = self.fuel_info(); self.translate_return_with(fuel_info) } /// Translates an unconditional `return` instruction given fuel information. - pub fn translate_return_with(&mut self, fuel_info: FuelInfo) -> Result<(), TranslationError> { + fn translate_return_with(&mut self, fuel_info: FuelInfo) -> Result<(), TranslationError> { let func_type = self.func_type(); let results = func_type.results(); let values = &mut self.alloc.buffer; @@ -2375,7 +2359,7 @@ impl<'parser> FuncTranslator<'parser> { } /// Translates a conditional `br_if` that targets the function enclosing `block`. - pub fn translate_return_if(&mut self, condition: Register) -> Result<(), TranslationError> { + fn translate_return_if(&mut self, condition: Register) -> Result<(), TranslationError> { bail_unreachable!(self); let len_results = self.func_type().results().len(); let fuel_info = self.fuel_info(); diff --git a/crates/wasmi/src/module/compile/mod.rs b/crates/wasmi/src/module/compile/mod.rs index 499f306f24..6c02b21aea 100644 --- a/crates/wasmi/src/module/compile/mod.rs +++ b/crates/wasmi/src/module/compile/mod.rs @@ -3,6 +3,7 @@ use super::{FuncIdx, ModuleResources}; use crate::{ engine::{ CompiledFunc, + FuncTranslator, FuncTranslatorAllocations, ReusableAllocations, ValidatingFuncTranslator, @@ -14,7 +15,7 @@ use wasmparser::{FuncValidator, FunctionBody, ValidatorResources}; mod block_type; -/// Translates the Wasm bytecode into `wasmi` bytecode. +/// Validates and translates the Wasm bytecode into `wasmi` IR bytecode. /// /// # Note /// @@ -26,7 +27,7 @@ mod block_type; /// /// # Errors /// -/// If the function body fails to validate. +/// If the function body fails to validate or translate the Wasm function body. pub fn translate<'parser>( func: FuncIdx, compiled_func: CompiledFunc, @@ -39,15 +40,37 @@ pub fn translate<'parser>( .translate() } +/// Translates the Wasm bytecode into `wasmi` IR bytecode. +/// +/// # Note +/// +/// - Uses the given `engine` as target for the translation. +/// - Uses the given module resources `res` as shared immutable data of the +/// already parsed and validated module parts required for the translation. +/// - Does _not_ validate the Wasm input. +/// +/// # Errors +/// +/// If the function body fails to translate the Wasm function body. +pub fn translate_unchecked<'parser>( + func: FuncIdx, + compiled_func: CompiledFunc, + func_body: FunctionBody<'parser>, + res: ModuleResources<'parser>, + allocations: FuncTranslatorAllocations, +) -> Result { + FunctionTranslator::new_unchecked(func, compiled_func, func_body, res, allocations)?.translate() +} + /// Translates Wasm bytecode into `wasmi` bytecode for a single Wasm function. -struct FunctionTranslator<'parser> { +struct FunctionTranslator<'parser, T> { /// The function body that shall be translated. func_body: FunctionBody<'parser>, - /// The interface to incrementally build up the `wasmi` bytecode function. - func_builder: ValidatingFuncTranslator<'parser>, + /// The underlying translator used for the translation (and validation) process. + translator: T, } -impl<'parser> FunctionTranslator<'parser> { +impl<'parser> FunctionTranslator<'parser, ValidatingFuncTranslator<'parser>> { /// Creates a new Wasm to `wasmi` bytecode function translator. fn new( func: FuncIdx, @@ -57,16 +80,38 @@ impl<'parser> FunctionTranslator<'parser> { res: ModuleResources<'parser>, allocations: FuncTranslatorAllocations, ) -> Result { - let func_builder = + let translator = ValidatingFuncTranslator::new(func, compiled_func, res, validator, allocations)?; Ok(Self { func_body, - func_builder, + translator, }) } +} + +impl<'parser> FunctionTranslator<'parser, FuncTranslator<'parser>> { + /// Creates a new Wasm to `wasmi` bytecode function translator. + fn new_unchecked( + func: FuncIdx, + compiled_func: CompiledFunc, + func_body: FunctionBody<'parser>, + res: ModuleResources<'parser>, + allocations: FuncTranslatorAllocations, + ) -> Result { + let translator = FuncTranslator::new(func, compiled_func, res, allocations)?; + Ok(Self { + func_body, + translator, + }) + } +} +impl<'parser, T> FunctionTranslator<'parser, T> +where + T: WasmTranslator<'parser>, +{ /// Starts translation of the Wasm stream into `wasmi` bytecode. - fn translate(mut self) -> Result { + fn translate(mut self) -> Result { self.translate_locals()?; let offset = self.translate_operators()?; let allocations = self.finish(offset)?; @@ -74,9 +119,9 @@ impl<'parser> FunctionTranslator<'parser> { } /// Finishes construction of the function and returns its [`CompiledFunc`]. - fn finish(mut self, offset: usize) -> Result { - self.func_builder.update_pos(offset); - self.func_builder.finish().map_err(Into::into) + fn finish(mut self, offset: usize) -> Result { + self.translator.update_pos(offset); + self.translator.finish().map_err(Into::into) } /// Translates local variables of the Wasm function. @@ -86,10 +131,10 @@ impl<'parser> FunctionTranslator<'parser> { for _ in 0..len_locals { let offset = reader.original_position(); let (amount, value_type) = reader.read()?; - self.func_builder.update_pos(offset); - self.func_builder.translate_locals(amount, value_type)?; + self.translator.update_pos(offset); + self.translator.translate_locals(amount, value_type)?; } - self.func_builder.finish_translate_locals()?; + self.translator.finish_translate_locals()?; Ok(()) } @@ -100,8 +145,8 @@ impl<'parser> FunctionTranslator<'parser> { let mut reader = self.func_body.get_operators_reader()?; while !reader.eof() { let pos = reader.original_position(); - self.func_builder.update_pos(pos); - reader.visit_operator(&mut self.func_builder)??; + self.translator.update_pos(pos); + reader.visit_operator(&mut self.translator)??; } reader.ensure_end()?; Ok(reader.original_position()) diff --git a/crates/wasmi/src/module/mod.rs b/crates/wasmi/src/module/mod.rs index 6c185ad692..ccbbd796a4 100644 --- a/crates/wasmi/src/module/mod.rs +++ b/crates/wasmi/src/module/mod.rs @@ -17,7 +17,7 @@ use self::{ export::ExternIdx, global::Global, import::{ExternTypeIdx, Import}, - parser::parse, + parser::{parse, parse_unchecked}, read::ReadError, }; pub use self::{ @@ -139,14 +139,49 @@ impl ModuleImports { impl Module { /// Creates a new Wasm [`Module`] from the given byte stream. /// + /// # Note + /// + /// This parses, validates and translates the Wasm bytecode yielded by `stream`. + /// /// # Errors /// - /// - If the `stream` cannot be decoded into a valid Wasm module. - /// - If unsupported Wasm proposals are encountered. + /// - If the `stream` cannot be parsed as a valid Wasm module. + /// - If the Wasm bytecode yielded by `stream` is not valid. + /// - If the Wasm bytecode yielded by `stream` violates restrictions + /// set in the [`Config`] used by the `engine`. + /// - If `wasmi` cannot translate the Wasm bytecode yielded by `stream`. + /// + /// [`Config`]: crate::Config pub fn new(engine: &Engine, stream: impl Read) -> Result { parse(engine, stream).map_err(Into::into) } + /// Creates a new Wasm [`Module`] from the given byte stream. + /// + /// # Note + /// + /// - This parses and translates the Wasm bytecode yielded by `stream`. + /// - This still validates Wasm bytecode outside of function bodies. + /// + /// # Safety + /// + /// - This does _not_ fully validate the Wasm bytecode yielded by `stream`. + /// - It is the caller's responsibility to call this function only with + /// a `stream` that yields fully valid Wasm bytecode. + /// - Additionally it is the caller's responsibility that the Wasm bytecode + /// yielded by `stream` must adhere to the restrictions set by the used + /// [`Config`] of the `engine`. + /// - Violating these rules may lead to undefined behavior. + /// + /// # Errors + /// + /// If the `stream` cannot be parsed as a valid Wasm module. + /// + /// [`Config`]: crate::Config + pub unsafe fn new_unchecked(engine: &Engine, stream: impl Read) -> Result { + unsafe { parse_unchecked(engine, stream).map_err(Into::into) } + } + /// Returns the [`Engine`] used during creation of the [`Module`]. pub fn engine(&self) -> &Engine { &self.engine diff --git a/crates/wasmi/src/module/parser.rs b/crates/wasmi/src/module/parser.rs index 96a37d4e3b..6b275f6b61 100644 --- a/crates/wasmi/src/module/parser.rs +++ b/crates/wasmi/src/module/parser.rs @@ -1,5 +1,5 @@ use super::{ - compile::translate, + compile::{translate, translate_unchecked}, export::ExternIdx, global::Global, import::{FuncTypeIdx, Import}, @@ -20,7 +20,7 @@ use crate::{ TableType, }; use alloc::{boxed::Box, vec::Vec}; -use core::{mem::replace, ops::Range}; +use core::{mem, ops::Range}; use wasmparser::{ Chunk, DataSectionReader, @@ -40,18 +40,30 @@ use wasmparser::{ WasmFeatures, }; -/// Parses and validates the given Wasm bytecode stream. +/// Parse, validate and translate the Wasm bytecode stream into Wasm IR bytecode. /// -/// Returns the compiled and validated Wasm [`Module`] upon success. -/// Uses the given [`Engine`] as the translation target of the process. +/// - Returns the fully compiled and validated Wasm [`Module`] upon success. +/// - Uses the given [`Engine`] as the translation target of the process. /// /// # Errors /// -/// If the Wasm bytecode stream fails to validate. +/// If the Wasm bytecode stream fails to parse, validate or translate. pub fn parse(engine: &Engine, stream: impl Read) -> Result { ModuleParser::new(engine).parse(stream) } +/// Parse and translate the Wasm bytecode stream into Wasm IR bytecode. +/// +/// - Returns the fully compiled Wasm [`Module`] upon success. +/// - Uses the given [`Engine`] as the translation target of the process. +/// +/// # Errors +/// +/// If the Wasm bytecode stream fails to parse or translate. +pub unsafe fn parse_unchecked(engine: &Engine, stream: impl Read) -> Result { + unsafe { ModuleParser::new(engine).parse_unchecked(stream) } +} + /// Context used to construct a WebAssembly module from a stream of bytes. pub struct ModuleParser<'engine> { /// The module builder used throughout stream parsing. @@ -93,7 +105,33 @@ impl<'engine> ModuleParser<'engine> { /// # Errors /// /// If the Wasm bytecode stream fails to validate. - pub fn parse(mut self, mut stream: impl Read) -> Result { + pub fn parse(self, stream: impl Read) -> Result { + self.parse_impl(true, stream) + } + + /// Starts parsing and validating the Wasm bytecode stream. + /// + /// Returns the compiled and validated Wasm [`Module`] upon success. + /// + /// # Errors + /// + /// If the Wasm bytecode stream fails to validate. + pub unsafe fn parse_unchecked(self, stream: impl Read) -> Result { + self.parse_impl(false, stream) + } + + /// Starts parsing and validating the Wasm bytecode stream. + /// + /// Returns the compiled and validated Wasm [`Module`] upon success. + /// + /// # Errors + /// + /// If the Wasm bytecode stream fails to validate. + fn parse_impl( + mut self, + validate_funcs: bool, + mut stream: impl Read, + ) -> Result { let mut buffer = Vec::new(); let mut eof = false; 'outer: loop { @@ -103,7 +141,7 @@ impl<'engine> ModuleParser<'engine> { continue 'outer; } Chunk::Parsed { consumed, payload } => { - eof = self.process_payload(payload)?; + eof = self.process_payload(payload, validate_funcs)?; // Cut away the parts from the intermediate buffer that have already been parsed. buffer.drain(..consumed); if eof { @@ -147,7 +185,11 @@ impl<'engine> ModuleParser<'engine> { /// - If Wasm validation of the payload fails. /// - If some unsupported Wasm proposal definition is encountered. /// - If `wasmi` limits are exceeded. - fn process_payload(&mut self, payload: Payload) -> Result { + fn process_payload( + &mut self, + payload: Payload, + validate_funcs: bool, + ) -> Result { match payload { Payload::Version { num, @@ -169,7 +211,9 @@ impl<'engine> ModuleParser<'engine> { Payload::DataSection(section) => self.process_data(section), Payload::CustomSection { .. } => Ok(()), Payload::CodeSectionStart { count, range, .. } => self.process_code_start(count, range), - Payload::CodeSectionEntry(func_body) => self.process_code_entry(func_body), + Payload::CodeSectionEntry(func_body) => { + self.process_code_entry(func_body, validate_funcs) + } Payload::UnknownSection { id, range, .. } => self.process_unknown(id, range), Payload::ModuleSection { parser: _, range } => { self.process_unsupported_component_model(range) @@ -489,21 +533,39 @@ impl<'engine> ModuleParser<'engine> { /// # Errors /// /// If the function body fails to validate. - fn process_code_entry(&mut self, func_body: FunctionBody) -> Result<(), ModuleError> { + fn process_code_entry( + &mut self, + func_body: FunctionBody, + validate_funcs: bool, + ) -> Result<(), ModuleError> { let (func, compiled_func) = self.next_func(); let validator = self.validator.code_section_entry(&func_body)?; - let module_resources = ModuleResources::new(&self.builder); - let dummy_allocations = ReusableAllocations::default(); - let allocations = replace(&mut self.allocations, dummy_allocations); - let allocations = translate( - func, - compiled_func, - func_body, - validator.into_validator(allocations.validation), - module_resources, - allocations.translation, - )?; - let _ = replace(&mut self.allocations, allocations); + let res = ModuleResources::new(&self.builder); + let allocations = mem::take(&mut self.allocations); + let allocations = match validate_funcs { + true => translate( + func, + compiled_func, + func_body, + validator.into_validator(allocations.validation), + res, + allocations.translation, + )?, + false => { + let translation = translate_unchecked( + func, + compiled_func, + func_body, + res, + allocations.translation, + )?; + ReusableAllocations { + translation, + validation: allocations.validation, + } + } + }; + _ = mem::replace(&mut self.allocations, allocations); Ok(()) }