wasmi_core-1.1.0/.cargo_vcs_info.json0000644000000001511046102023000132020ustar { "git": { "sha1": "8273dfb09d493971b7bb12fe614d740cdc857175" }, "path_in_vcs": "crates/core" }wasmi_core-1.1.0/Cargo.lock0000644000000005631046102023000111640ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "libm" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "wasmi_core" version = "1.1.0" dependencies = [ "libm", ] wasmi_core-1.1.0/Cargo.toml0000644000000025121046102023000112030ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.86" name = "wasmi_core" version = "1.1.0" authors = ["Robin Freyler "] build = false exclude = ["tests"] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Core primitives for the wasmi WebAssembly interpreter" documentation = "https://docs.rs/wasmi_core" readme = "README.md" keywords = [ "wasm", "webassembly", "interpreter", "vm", ] categories = [ "wasm", "no-std", "virtualization", ] license = "MIT/Apache-2.0" repository = "https://github.com/wasmi-labs/wasmi" [package.metadata.cargo-udeps.ignore] normal = ["libm"] [package.metadata.docs.rs] all-features = true [features] default = ["std"] simd = [] std = [] [lib] name = "wasmi_core" path = "src/lib.rs" [dependencies.libm] version = "0.2.11" default-features = false wasmi_core-1.1.0/Cargo.toml.orig000064400000000000000000000016401046102023000146430ustar 00000000000000[package] name = "wasmi_core" version.workspace = true rust-version.workspace = true documentation = "https://docs.rs/wasmi_core" description = "Core primitives for the wasmi WebAssembly interpreter" authors.workspace = true repository.workspace = true edition.workspace = true readme.workspace = true license.workspace = true keywords.workspace = true categories.workspace = true exclude.workspace = true [dependencies] libm = { version = "0.2.11", default-features = false } [features] default = ["std"] # Use `no-default-features` for a `no_std` build. std = [] # Enables the Wasm `simd` proposal. # # This also changes the size of `UntypedVal` from 64-bit to 128-bit # which may have significant impact on performance and memory usage. simd = [] [package.metadata.cargo-udeps.ignore] # cargo-udeps cannot detect that libm is used for no_std targets only. normal = ["libm"] [package.metadata.docs.rs] all-features = true wasmi_core-1.1.0/README.md000064400000000000000000000247611046102023000132440ustar 00000000000000 | Continuous Integration | Test Coverage | Documentation | Crates.io | |:----------------------:|:--------------------:|:----------------:|:--------------------:| | [![ci][1]][2] | [![codecov][3]][4] | [![docs][5]][6] | [![crates][7]][8] | [1]: https://github.com/wasmi-labs/wasmi/actions/workflows/rust.yml/badge.svg [2]: https://github.com/wasmi-labs/wasmi/actions/workflows/rust.yml [3]: https://codecov.io/gh/wasmi-labs/wasmi/branch/main/badge.svg [4]: https://codecov.io/gh/wasmi-labs/wasmi/branch/main [5]: https://docs.rs/wasmi/badge.svg [6]: https://docs.rs/wasmi [7]: https://img.shields.io/crates/v/wasmi.svg [8]: https://crates.io/crates/wasmi [license-mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg [license-apache-badge]: https://img.shields.io/badge/license-APACHE-orange.svg # Wasmi - WebAssembly (Wasm) Interpreter

Wasmi is an efficient and lightweight WebAssembly interpreter with a focus on constrained and embedded systems. ## Distinct Features - Simple, correct and deterministic execution of WebAssembly. - Efficient and cross-platform WebAssembly runtime for [`no_std` embedded environments](https://doc.rust-lang.org/stable/rustc/platform-support.html). - Compiler/JIT bomb resisting translation. - Loosely mirrors the [Wasmtime API](https://docs.rs/wasmtime/) to act as drop-in replacement. - 100% WebAssembly spec testsuite compliance. - Built-in support for fuel metering. - Supports the official [Wasm C-API](https://github.com/WebAssembly/wasm-c-api). ## Security Audits Wasmi is suitable for safety critical use cases and has been audited twice. | Wasmi Version(s) | Auditor | Contractor | Report | |--:|:--|:--|:--| | `0.36.0`-`0.38.0` | [Runtime Verification Inc.] | [Stellar Development Foundation] | [PDF](./resources/audit-2024-11-27.pdf) | | `0.31.0` | [SRLabs] | [Parity Technologies] | [PDF](./resources/audit-2023-12-20.pdf) | [Wasmtime]: https://github.com/bytecodealliance/wasmtime [SRLabs]: https://www.srlabs.de/ [Runtime Verification Inc.]: https://runtimeverification.com/ [Stellar Development Foundation]: https://stellar.org/foundation [Parity Technologies]: https://www.parity.io/ ## Docs - 📖 [Usage Guide](./docs/usage.md): learn how to use the [Wasmi API](https://crates.io/crates/wasmi) properly. - 🛠️ [Development Guide](./docs/developement.md): learn how to develop for Wasmi. - ✨ [Crate Features](https://docs.rs/wasmi/latest/wasmi/#crate-features): learn about `wasmi` crate features. ## WebAssembly Features | | WebAssembly Proposal | | | | WebAssembly Proposal | | |:-:|:--|:--|:-:|:--|:--|:--| | ✅ | [`mutable-global`] | ≥ `0.14.0` | | ✅ | [`custom-page-sizes`] | [≥ `0.41.0`][(#1197)] | | ✅ | [`saturating-float-to-int`] | ≥ `0.14.0` | | ✅ | [`memory64`] | [≥ `0.41.0`][(#1357)] | | ✅ | [`sign-extension`] | ≥ `0.14.0` | | ✅ | [`wide-arithmetic`] | [≥ `0.42.0`][(#1369)] | | ✅ | [`multi-value`] | ≥ `0.14.0` | | ✅ | [`simd`] | [≥ `0.43.0`][(#1364)] | | ✅ | [`bulk-memory`] | [≥ `0.24.0`][(#628)] | | ✅ | [`relaxed-simd`] | [≥ `0.44.0`][(#1443)] | | ✅ | [`reference-types`] | [≥ `0.24.0`][(#635)] | | 📅 | [`function-references`] | [Tracking Issue][(#774)] | | ✅ | [`tail-calls`] | [≥ `0.28.0`][(#683)] | | 📅 | [`gc`] | [Tracking Issue][(#775)] | | ✅ | [`extended-const`] | [≥ `0.29.0`][(#707)] | | 📅 | [`threads`] | [Tracking Issue][(#777)] | | ✅ | [`multi-memory`] | [≥ `0.37.0`][(#1191)] | | 📅 | [`exception-handling`] | [Tracking Issue][(#1037)] | | | Embeddings | | |:-:|:--|:--| | ✅ | [WASI] | WASI (`wasip1`) support via the [`wasmi_wasi` crate]. | | ✅ | [C-API] | Official Wasm C-API support via the [`wasmi_c_api_impl` crate]. | [`mutable-global`]: https://github.com/WebAssembly/mutable-global [`saturating-float-to-int`]: https://github.com/WebAssembly/nontrapping-float-to-int-conversions [`sign-extension`]: https://github.com/WebAssembly/sign-extension-ops [`multi-value`]: https://github.com/WebAssembly/multi-value [`reference-types`]: https://github.com/WebAssembly/reference-types [`bulk-memory`]: https://github.com/WebAssembly/bulk-memory-operations [`simd` ]: https://github.com/webassembly/simd [`tail-calls`]: https://github.com/WebAssembly/tail-call [`extended-const`]: https://github.com/WebAssembly/extended-const [`function-references`]: https://github.com/WebAssembly/function-references [`gc`]: https://github.com/WebAssembly/gc [`multi-memory`]: https://github.com/WebAssembly/multi-memory [`threads`]: https://github.com/WebAssembly/threads [`relaxed-simd`]: https://github.com/WebAssembly/relaxed-simd [`exception-handling`]: https://github.com/WebAssembly/exception-handling [`custom-page-sizes`]: https://github.com/WebAssembly/custom-page-sizes [`memory64`]: https://github.com/WebAssembly/memory64 [`wide-arithmetic`]: https://github.com/WebAssembly/wide-arithmetic [WASI]: https://github.com/WebAssembly/WASI [C-API]: https://github.com/WebAssembly/wasm-c-api [`wasmi_wasi` crate]: ./crates/wasi [`wasmi_c_api_impl` crate]: ./crates/c_api [(#363)]: https://github.com/wasmi-labs/wasmi/issues/363 [(#364)]: https://github.com/wasmi-labs/wasmi/issues/364 [(#496)]: https://github.com/wasmi-labs/wasmi/issues/496 [(#628)]: https://github.com/wasmi-labs/wasmi/pull/628 [(#635)]: https://github.com/wasmi-labs/wasmi/pull/635 [(#638)]: https://github.com/wasmi-labs/wasmi/pull/638 [(#683)]: https://github.com/wasmi-labs/wasmi/pull/683 [(#707)]: https://github.com/wasmi-labs/wasmi/pull/707 [(#774)]: https://github.com/wasmi-labs/wasmi/pull/774 [(#775)]: https://github.com/wasmi-labs/wasmi/pull/775 [(#776)]: https://github.com/wasmi-labs/wasmi/pull/776 [(#777)]: https://github.com/wasmi-labs/wasmi/pull/777 [(#1037)]: https://github.com/wasmi-labs/wasmi/issues/1137 [(#1197)]: https://github.com/wasmi-labs/wasmi/issues/1197 [(#1191)]: https://github.com/wasmi-labs/wasmi/issues/1191 [(#1357)]: https://github.com/wasmi-labs/wasmi/issues/1357 [(#1364)]: https://github.com/wasmi-labs/wasmi/issues/1364 [(#1369)]: https://github.com/wasmi-labs/wasmi/issues/1369 [(#1443)]: https://github.com/wasmi-labs/wasmi/pull/1443 ## Used by If you want your project on this list [please inform me](mailto:robin.freyler@gmail.com) about you project and how Wasmi is used. Stellar Soroban   Wasmer   Firefly Zero   Typst   Orbitinghail   Smoldot   Munal OS   icu4x   Ayaka   Project Oak   ## Sponsors Special thanks to the past and present sponsors of the Wasmi project.
Sponsoring since Oct. 2024
Sponsored until Oct. 2024
## License Licensed under either of * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) * MIT license ([LICENSE-MIT](LICENSE-MIT) or ) at your option. ## Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. wasmi_core-1.1.0/src/float.rs000064400000000000000000000055231046102023000142220ustar 00000000000000macro_rules! float { ( $( #[$docs:meta] )* struct $name:ident($prim:ty as $bits:ty); ) => { $(#[$docs])* #[derive(Copy, Clone)] pub struct $name($bits); impl $name { /// Creates a float from its underlying bits. #[inline] pub fn from_bits(other: $bits) -> Self { Self(other) } /// Returns the underlying bits of the float. #[inline] pub fn to_bits(self) -> $bits { self.0 } /// Creates a float from the respective primitive float type. #[inline] pub fn from_float(float: $prim) -> Self { Self::from_bits(float.to_bits()) } /// Returns the respective primitive float type. #[inline] pub fn to_float(self) -> $prim { <$prim>::from_bits(self.to_bits()) } } impl ::core::convert::From<$prim> for $name { #[inline] fn from(float: $prim) -> $name { Self::from_float(float) } } impl ::core::convert::From<$name> for $prim { #[inline] fn from(float: $name) -> $prim { float.to_float() } } impl ::core::cmp::PartialEq<$name> for $name { #[inline] fn eq(&self, other: &$name) -> ::core::primitive::bool { self.to_float().eq(&other.to_float()) } } impl ::core::cmp::PartialOrd<$name> for $name { #[inline] fn partial_cmp(&self, other: &$name) -> ::core::option::Option<::core::cmp::Ordering> { self.to_float().partial_cmp(&other.to_float()) } } impl ::core::fmt::Debug for $name { fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { if self.to_float().is_nan() { return core::write!(f, "nan:0x{:X?}", self.to_bits()) } <$prim as ::core::fmt::Debug>::fmt( &<$prim as ::core::convert::From>::from(*self), f, ) } } impl ::core::fmt::Display for $name { fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { if self.to_float().is_nan() { return core::write!(f, "nan:0x{:X?}", self.to_bits()) } <$prim as ::core::fmt::Display>::fmt( &<$prim as ::core::convert::From>::from(*self), f, ) } } }; } float! { /// A 32-bit `f32` type. struct F32(f32 as u32); } float! { /// A 64-bit `f64` type. struct F64(f64 as u64); } wasmi_core-1.1.0/src/fuel.rs000064400000000000000000000232301046102023000140430ustar 00000000000000use crate::UntypedVal; use alloc::sync::Arc; use core::{ error::Error, fmt::{self, Debug}, mem, num::NonZeroU64, }; /// Fuel costs for Wasmi IR instructions. pub trait FuelCosts { /// Returns the base fuel costs for all Wasmi IR instructions. fn base(&self) -> u64; /// Returns the base fuel costs for all Wasmi IR `load` instructions. fn load(&self) -> u64 { self.base() } /// Returns the base fuel costs for all Wasmi IR `instance` instructions. /// /// # Note /// /// Entity-based instructions access or modify instance related data, /// such as globals, memories, tables or functions. fn instance(&self) -> u64 { self.base() } /// Returns the base fuel costs for all Wasmi IR `store` instructions. fn store(&self) -> u64 { self.base() } /// Returns the base fuel costs for all Wasmi IR `call` instructions. fn call(&self) -> u64 { self.base() } /// Returns the base fuel costs for all Wasmi IR `simd` instructions. fn simd(&self) -> u64 { self.base() } /// Returns the amount of bytes that can be copied for a single unit of fuel. fn bytes_per_fuel(&self) -> NonZeroU64; } /// Implementation of default [`FuelCostsProvider`]. struct DefaultFuelCosts; impl FuelCosts for DefaultFuelCosts { fn base(&self) -> u64 { 1 } fn bytes_per_fuel(&self) -> NonZeroU64 { NonZeroU64::new(64).unwrap() } } /// Type storing all kinds of fuel costs of instructions. #[derive(Default, Clone)] pub struct FuelCostsProvider { /// Optional custom fuel costs. custom: Option>, } impl Debug for FuelCostsProvider { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let base = self.base(); let instance = self.instance(); let load = self.load(); let store = self.store(); let call = self.call(); let simd = self.simd(); let bytes_per_fuel = self.bytes_per_fuel(); f.debug_struct("FuelCostsProvider") .field("base", &base) .field("instance", &instance) .field("load", &load) .field("store", &store) .field("call", &call) .field("simd", &simd) .field("bytes_per_fuel", &bytes_per_fuel) .finish() } } impl FuelCostsProvider { /// Applies `f` to either `self.custom` or [`DefaultFuelCosts`] if `self.custom` is `None`. fn apply(&self, f: impl FnOnce(&dyn FuelCosts) -> u64) -> u64 { match self.custom.as_deref() { Some(costs) => f(costs), None => f(&DefaultFuelCosts), } } /// Returns the base fuel costs for all Wasmi IR instructions. pub fn base(&self) -> u64 { self.apply(|c| c.base()) } /// Returns the fuel costs for all Wasmi IR `instance` related instructions. pub fn instance(&self) -> u64 { self.apply(|c: &dyn FuelCosts| c.instance()) } /// Returns the fuel costs for all Wasmi IR `load` instructions. pub fn load(&self) -> u64 { self.apply(|c: &dyn FuelCosts| c.load()) } /// Returns the fuel costs for all Wasmi IR `store` instructions. pub fn store(&self) -> u64 { self.apply(|c: &dyn FuelCosts| c.store()) } /// Returns the fuel costs for all Wasmi IR `call` instructions. pub fn call(&self) -> u64 { self.apply(|c: &dyn FuelCosts| c.call()) } /// Returns the fuel costs for all Wasmi IR `simd` instructions. pub fn simd(&self) -> u64 { self.apply(|c: &dyn FuelCosts| c.simd()) } /// Returns the number of bytes that can be copied per unit of fuel. fn bytes_per_fuel(&self) -> NonZeroU64 { match self.custom.as_deref() { Some(costs) => costs.bytes_per_fuel(), None => DefaultFuelCosts.bytes_per_fuel(), } } /// Returns the fuel costs for `len_bytes` byte copies in Wasmi IR. /// /// # Note /// /// - On overflow this returns [`u64::MAX`]. /// - The following Wasmi IR instructions may make use of this: /// - `memory.grow` /// - `memory.copy` /// - `memory.fill` /// - `memory.init` pub fn fuel_for_copying_bytes(&self, len_bytes: u64) -> u64 { len_bytes / self.bytes_per_fuel() } /// Returns the fuel costs for copying `len_values` [`UntypedVal`] items. /// /// # Note /// /// - On overflow this returns [`u64::MAX`]. /// - [`UntypedVal`] might be 64-bit or 128-bit depending on the crate's config. /// - The following Wasmi IR instructions may make use of this: /// - calls (parameter passing) /// - `copy_span` /// - `copy_many` /// - `return_span` /// - `return_many` /// - `table.grow` (+ variants) /// - `table.copy` (+ variants) /// - `table.fill` (+ variants) /// - `table.init` (+ variants) pub fn fuel_for_copying_values(&self, len_values: u64) -> u64 { let Ok(size_of_val) = u64::try_from(mem::size_of::()) else { return u64::MAX; }; let copied_bytes = len_values.saturating_mul(size_of_val); self.fuel_for_copying_bytes(copied_bytes) } } /// An error that may be encountered when using [`Fuel`]. #[derive(Debug, Clone)] pub enum FuelError { /// Returned by some [`Fuel`] methods when fuel metering is disabled. FuelMeteringDisabled, /// Raised when trying to consume more fuel than is available. OutOfFuel { required_fuel: u64 }, } impl Error for FuelError {} impl fmt::Display for FuelError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::FuelMeteringDisabled => write!(f, "fuel metering is disabled"), Self::OutOfFuel { required_fuel } => write!(f, "ouf of fuel. required={required_fuel}"), } } } impl FuelError { /// Returns an error indicating that fuel metering has been disabled. /// /// # Note /// /// This method exists to indicate that this execution path is cold. #[cold] pub fn fuel_metering_disabled() -> Self { Self::FuelMeteringDisabled } /// Returns an error indicating that too much fuel has been consumed. /// /// # Note /// /// This method exists to indicate that this execution path is cold. #[cold] pub fn out_of_fuel(required_fuel: u64) -> Self { Self::OutOfFuel { required_fuel } } } /// The remaining and consumed fuel counters. #[derive(Debug)] pub struct Fuel { /// The remaining fuel. remaining: u64, /// This is `true` if fuel metering is enabled. enabled: bool, /// The fuel costs. costs: FuelCostsProvider, } impl Fuel { /// Creates a new [`Fuel`]. pub fn new(enabled: bool, costs: FuelCostsProvider) -> Self { Self { remaining: 0, enabled, costs, } } /// Returns `true` if fuel metering is enabled. fn is_fuel_metering_enabled(&self) -> bool { self.enabled } /// Returns `Ok` if fuel metering is enabled. /// /// Returns descriptive [`FuelError`] otherwise. /// /// # Errors /// /// If fuel metering is disabled. fn check_fuel_metering_enabled(&self) -> Result<(), FuelError> { if !self.is_fuel_metering_enabled() { return Err(FuelError::fuel_metering_disabled()); } Ok(()) } /// Sets the remaining fuel to `fuel`. /// /// # Errors /// /// If fuel metering is disabled. pub fn set_fuel(&mut self, fuel: u64) -> Result<(), FuelError> { self.check_fuel_metering_enabled()?; self.remaining = fuel; Ok(()) } /// Returns the remaining fuel. /// /// # Errors /// /// If fuel metering is disabled. pub fn get_fuel(&self) -> Result { self.check_fuel_metering_enabled()?; Ok(self.remaining) } /// Synthetically consumes an amount of [`Fuel`]. /// /// Returns the remaining amount of [`Fuel`] after this operation. /// /// # Note /// /// - This does _not_ check if fuel metering is enabled. /// - This API is intended for use cases where it is clear that fuel metering is /// enabled and where a check would incur unnecessary overhead in a hot path. /// An example of this is the execution of consume fuel instructions since /// those only exist if fuel metering is enabled. /// /// # Errors /// /// If out of fuel. pub fn consume_fuel_unchecked(&mut self, delta: u64) -> Result { self.remaining = self .remaining .checked_sub(delta) .ok_or(FuelError::out_of_fuel(delta))?; Ok(self.remaining) } /// Consumes an amount of [`Fuel`]. /// /// Returns the remaining amount of [`Fuel`] after this operation. /// /// # Errors /// /// - If fuel metering is disabled. /// - If out of fuel. pub fn consume_fuel( &mut self, f: impl FnOnce(&FuelCostsProvider) -> u64, ) -> Result { self.check_fuel_metering_enabled()?; self.consume_fuel_unchecked(f(&self.costs)) } /// Consumes an amount of [`Fuel`] if fuel metering is enabled. /// /// # Note /// /// This does nothing if fuel metering is disabled. /// /// # Errors /// /// - If out of fuel. pub fn consume_fuel_if( &mut self, f: impl FnOnce(&FuelCostsProvider) -> u64, ) -> Result<(), FuelError> { if !self.is_fuel_metering_enabled() { return Ok(()); } self.consume_fuel_unchecked(f(&self.costs))?; Ok(()) } } wasmi_core-1.1.0/src/func_type.rs000064400000000000000000000275661046102023000151240ustar 00000000000000use crate::ValType; use alloc::{sync::Arc, vec::Vec}; use core::{fmt, fmt::Display}; /// Errors that can occur upon type checking function signatures. #[derive(Debug, Copy, Clone)] pub enum FuncTypeError { /// Too many function parameters. TooManyFunctionParams, /// Too many function results. TooManyFunctionResults, } impl core::error::Error for FuncTypeError {} impl Display for FuncTypeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { FuncTypeError::TooManyFunctionParams => { write!(f, "encountered a function with too many parameters") } FuncTypeError::TooManyFunctionResults => { write!(f, "encountered a function with too many results") } } } } /// A function type representing a function's parameter and result types. /// /// # Note /// /// Can be cloned cheaply. #[derive(Clone, PartialOrd, Ord, PartialEq, Eq, Hash)] pub struct FuncType { /// The inner function type internals. inner: FuncTypeInner, } /// Internal details of [`FuncType`]. #[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)] pub enum FuncTypeInner { /// Stores the value types of the parameters and results inline. Inline { /// The number of parameters. len_params: u8, /// The number of results. len_results: u8, /// The parameter types, followed by the result types, followed by unspecified elements. params_results: [ValType; Self::INLINE_SIZE], }, /// Stores the value types of the parameters and results on the heap. Big { /// The number of parameters. len_params: u16, /// Combined parameter and result types allocated on the heap. params_results: Arc<[ValType]>, }, } impl FuncTypeInner { /// The inline buffer size on 32-bit platforms. /// /// # Note /// /// On 32-bit platforms we target a `size_of()` of 16 bytes. #[cfg(target_pointer_width = "32")] const INLINE_SIZE: usize = 14; /// The inline buffer size on 64-bit platforms. /// /// # Note /// /// On 64-bit platforms we target a `size_of()` of 24 bytes. #[cfg(target_pointer_width = "64")] const INLINE_SIZE: usize = 21; /// The maximum number of parameter types allowed of a [`FuncType`]. const MAX_LEN_PARAMS: usize = 1_000; /// The maximum number of result types allowed of a [`FuncType`]. const MAX_LEN_RESULTS: usize = 1_000; /// Creates a new [`FuncTypeInner`]. /// /// # Errors /// /// If an out of bounds number of parameters or results are given. pub fn new(params: P, results: R) -> Result where P: IntoIterator, R: IntoIterator,

::IntoIter: Iterator + ExactSizeIterator, ::IntoIter: Iterator + ExactSizeIterator, { let mut params = params.into_iter(); let mut results = results.into_iter(); let len_params = params.len(); let len_results = results.len(); if len_params > Self::MAX_LEN_PARAMS { return Err(FuncTypeError::TooManyFunctionParams); } if len_results > Self::MAX_LEN_RESULTS { return Err(FuncTypeError::TooManyFunctionResults); } if let Some(small) = Self::try_new_small(&mut params, &mut results) { return Ok(small); } let Ok(len_params) = u16::try_from(len_params) else { unreachable!("already ensured that `len_params` is well within bounds of `u16::MAX`") }; let mut params_results = params.collect::>(); params_results.extend(results); Ok(Self::Big { params_results: params_results.into(), len_params, }) } /// Tries to create a [`FuncTypeInner::Inline`] variant from the given inputs. /// /// # Note /// /// - Returns `None` if creation was not possible. /// - Does not mutate `params` or `results` if this method returns `None`. pub fn try_new_small(params: &mut P, results: &mut R) -> Option where P: Iterator + ExactSizeIterator, R: Iterator + ExactSizeIterator, { let params = params.into_iter(); let results = results.into_iter(); let len_params = u8::try_from(params.len()).ok()?; let len_results = u8::try_from(results.len()).ok()?; let len_inout = len_params.checked_add(len_results)?; if usize::from(len_inout) > Self::INLINE_SIZE { return None; } let mut params_results = [ValType::I32; Self::INLINE_SIZE]; let cells = &mut params_results[..usize::from(len_inout)]; let (cells_params, cells_results) = cells.split_at_mut(usize::from(len_params)); for (cell, param) in cells_params.iter_mut().zip(params) { *cell = param; } for (cell, result) in cells_results.iter_mut().zip(results) { *cell = result; } Some(Self::Inline { len_params, len_results, params_results, }) } /// Returns the parameter types of the function type. pub fn params(&self) -> &[ValType] { match self { FuncTypeInner::Inline { len_params, params_results, .. } => ¶ms_results[..usize::from(*len_params)], FuncTypeInner::Big { len_params, params_results, } => ¶ms_results[..usize::from(*len_params)], } } /// Returns the result types of the function type. pub fn results(&self) -> &[ValType] { match self { FuncTypeInner::Inline { len_params, len_results, params_results, .. } => { let start_results = usize::from(*len_params); let end_results = start_results + usize::from(*len_results); ¶ms_results[start_results..end_results] } FuncTypeInner::Big { len_params, params_results, } => ¶ms_results[usize::from(*len_params)..], } } /// Returns the number of parameter types of the function type. pub fn len_params(&self) -> u16 { match self { FuncTypeInner::Inline { len_params, .. } => u16::from(*len_params), FuncTypeInner::Big { len_params, .. } => *len_params, } } /// Returns the number of result types of the function type. pub fn len_results(&self) -> u16 { match self { FuncTypeInner::Inline { len_results, .. } => u16::from(*len_results), FuncTypeInner::Big { len_params, params_results, } => { // Note: this cast is safe since the number of parameters and results // are both bounded to 1000 maximum and `params_results` // thus contains 2000 items at most. let Ok(len_buffer) = u16::try_from(params_results.len()) else { panic!("the size of a `FuncType` buffer must fit into a `u16`") }; let len_params = *len_params; len_buffer - len_params } } } /// Returns the pair of parameter and result types of the function type. pub(crate) fn params_results(&self) -> (&[ValType], &[ValType]) { match self { FuncTypeInner::Inline { len_params, len_results, params_results, } => { let len_params = usize::from(*len_params); let len_results = usize::from(*len_results); params_results[..len_params + len_results].split_at(len_params) } FuncTypeInner::Big { len_params, params_results, } => params_results.split_at(usize::from(*len_params)), } } } #[test] fn size_of_func_type() { #[cfg(target_pointer_width = "32")] assert!(core::mem::size_of::() <= 16); #[cfg(target_pointer_width = "64")] assert!(core::mem::size_of::() <= 24); } impl fmt::Debug for FuncType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("FuncType") .field("params", &self.params()) .field("results", &self.results()) .finish() } } impl FuncType { /// Creates a new [`FuncType`]. /// /// # Errors /// /// If an out of bounds number of parameters or results are given. pub fn new(params: P, results: R) -> Result where P: IntoIterator, R: IntoIterator,

::IntoIter: Iterator + ExactSizeIterator, ::IntoIter: Iterator + ExactSizeIterator, { let inner = FuncTypeInner::new(params, results)?; Ok(Self { inner }) } /// Returns the parameter types of the function type. pub fn params(&self) -> &[ValType] { self.inner.params() } /// Returns the result types of the function type. pub fn results(&self) -> &[ValType] { self.inner.results() } /// Returns the number of parameter types of the function type. pub fn len_params(&self) -> u16 { self.inner.len_params() } /// Returns the number of result types of the function type. pub fn len_results(&self) -> u16 { self.inner.len_results() } /// Returns the pair of parameter and result types of the function type. pub fn params_results(&self) -> (&[ValType], &[ValType]) { self.inner.params_results() } } #[cfg(test)] mod tests { use super::*; #[test] fn new_empty_works() { let ft = FuncType::new([], []).unwrap(); assert!(ft.params().is_empty()); assert!(ft.results().is_empty()); assert_eq!(ft.params(), ft.params_results().0); assert_eq!(ft.results(), ft.params_results().1); } #[test] fn new_inline_works() { let types = [ &[ValType::I32][..], &[ValType::I64][..], &[ValType::F32][..], &[ValType::F64][..], &[ValType::I32, ValType::I32][..], &[ValType::I32, ValType::I32, ValType::I32][..], &[ValType::I32, ValType::I32, ValType::I32, ValType::I32][..], &[ ValType::I32, ValType::I32, ValType::I32, ValType::I32, ValType::I32, ValType::I32, ValType::I32, ValType::I32, ][..], &[ValType::I32, ValType::I64, ValType::F32, ValType::F64][..], ]; for params in types { for results in types { let params_iter = params.iter().copied(); let results_iter = results.iter().copied(); let ft = FuncType::new(params_iter, results_iter).unwrap(); assert_eq!(ft.params(), params); assert_eq!(ft.results(), results); assert_eq!(ft.params(), ft.params_results().0); assert_eq!(ft.results(), ft.params_results().1); } } } #[test] fn new_big_works() { let params = [ValType::I32; 100]; let results = [ValType::I64; 100]; let params_iter = params.iter().copied(); let results_iter = results.iter().copied(); let ft = FuncType::new(params_iter, results_iter).unwrap(); assert_eq!(ft.params(), params); assert_eq!(ft.results(), results); assert_eq!(ft.params(), ft.params_results().0); assert_eq!(ft.results(), ft.params_results().1); } } wasmi_core-1.1.0/src/global.rs000064400000000000000000000072711046102023000143570ustar 00000000000000use crate::{TypedVal, UntypedVal, ValType}; use core::{error::Error, fmt, fmt::Display, ptr::NonNull}; /// An error that may occur upon operating on global variables. #[derive(Debug)] #[non_exhaustive] pub enum GlobalError { /// Occurs when trying to write to an immutable global variable. ImmutableWrite, /// Occurs when trying writing a value with mismatching type to a global variable. TypeMismatch, } impl Error for GlobalError {} impl Display for GlobalError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let message = match self { Self::ImmutableWrite => "tried to write to immutable global variable", Self::TypeMismatch => "tried to write value of non-matching type to global variable", }; write!(f, "{message}") } } /// The mutability of a global variable. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum Mutability { /// The value of the global variable is a constant. Const, /// The value of the global variable is mutable. Var, } impl Mutability { /// Returns `true` if this mutability is [`Mutability::Const`]. pub fn is_const(&self) -> bool { matches!(self, Self::Const) } /// Returns `true` if this mutability is [`Mutability::Var`]. pub fn is_mut(&self) -> bool { matches!(self, Self::Var) } } /// The type of a global variable. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct GlobalType { /// The value type of the global variable. content: ValType, /// The mutability of the global variable. mutability: Mutability, } impl GlobalType { /// Creates a new [`GlobalType`] from the given [`ValType`] and [`Mutability`]. pub fn new(content: ValType, mutability: Mutability) -> Self { Self { content, mutability, } } /// Returns the [`ValType`] of the global variable. pub fn content(&self) -> ValType { self.content } /// Returns the [`Mutability`] of the global variable. pub fn mutability(&self) -> Mutability { self.mutability } } /// A global variable entity. #[derive(Debug)] pub struct Global { /// The current value of the global variable. value: UntypedVal, /// The type of the global variable. ty: GlobalType, } impl Global { /// Creates a new global entity with the given initial value and mutability. pub fn new(initial_value: TypedVal, mutability: Mutability) -> Self { Self { ty: GlobalType::new(initial_value.ty(), mutability), value: initial_value.into(), } } /// Returns the [`GlobalType`] of the global variable. pub fn ty(&self) -> GlobalType { self.ty } /// Sets a new value to the global variable. /// /// # Errors /// /// - If the [`Global`] is immutable. /// - If `new_value` does not match the type of the [`Global`]. pub fn set(&mut self, new_value: TypedVal) -> Result<(), GlobalError> { if !self.ty().mutability().is_mut() { return Err(GlobalError::ImmutableWrite); } if self.ty().content() != new_value.ty() { return Err(GlobalError::TypeMismatch); } self.value = new_value.into(); Ok(()) } /// Returns the current [`TypedVal`] of the [`Global`]. pub fn get(&self) -> TypedVal { TypedVal::new(self.ty().content(), self.value) } /// Returns the current [`UntypedVal`] of the [`Global`]. pub fn get_untyped(&self) -> &UntypedVal { &self.value } /// Returns a pointer to the [`UntypedVal`] of the [`Global`]. pub fn get_untyped_ptr(&mut self) -> NonNull { NonNull::from(&mut self.value) } } wasmi_core-1.1.0/src/hint.rs000064400000000000000000000006471046102023000140610ustar 00000000000000/// Indicates that the calling scope is unlikely to be executed. #[cold] #[inline] pub fn cold() {} /// Indicates that the condition is likely `true`. #[inline] pub fn likely(condition: bool) -> bool { if !condition { cold() } condition } /// Indicates that the condition is unlikely `true`. #[inline] pub fn unlikely(condition: bool) -> bool { if condition { cold() } condition } wasmi_core-1.1.0/src/host_error.rs000064400000000000000000000060531046102023000153020ustar 00000000000000use alloc::boxed::Box; use core::{ any::{type_name, Any}, fmt::{Debug, Display}, }; /// Trait that allows the host to return custom error. /// /// It should be useful for representing custom traps, /// troubles at instantiation time or other host specific conditions. /// /// Types that implement this trait can automatically be converted to `wasmi::Error` and `wasmi::Trap` /// and will be represented as a boxed `HostError`. You can then use the various methods on `wasmi::Error` /// to get your custom error type back /// /// # Examples /// /// ```rust /// use std::fmt; /// use wasmi_core::{Trap, HostError}; /// /// #[derive(Debug, Copy, Clone)] /// struct MyError { /// code: u32, /// } /// /// impl fmt::Display for MyError { /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { /// write!(f, "MyError, code={}", self.code) /// } /// } /// /// impl HostError for MyError { } /// /// fn failable_fn() -> Result<(), Trap> { /// let my_error = MyError { code: 42 }; /// // Note how you can just convert your errors to `wasmi::Error` /// Err(my_error.into()) /// } /// /// // Get a reference to the concrete error /// match failable_fn() { /// Err(trap) => { /// let my_error: &MyError = trap.downcast_ref().unwrap(); /// assert_eq!(my_error.code, 42); /// } /// _ => panic!(), /// } /// /// // get the concrete error itself /// match failable_fn() { /// Err(err) => { /// let my_error = match err.downcast_ref::() { /// Some(host_error) => host_error.clone(), /// None => panic!("expected host error `MyError` but found: {}", err), /// }; /// assert_eq!(my_error.code, 42); /// } /// _ => panic!(), /// } /// ``` pub trait HostError: 'static + Display + Debug + Any + Send + Sync {} impl dyn HostError { /// Returns `true` if `self` is of type `T`. pub fn is(&self) -> bool { (self as &dyn Any).is::() } /// Downcasts the [`HostError`] into a shared reference to a `T` if possible. /// /// Returns `None` otherwise. #[inline] pub fn downcast_ref(&self) -> Option<&T> { (self as &dyn Any).downcast_ref::() } /// Downcasts the [`HostError`] into an exclusive reference to a `T` if possible. /// /// Returns `None` otherwise. #[inline] pub fn downcast_mut(&mut self) -> Option<&mut T> { (self as &mut dyn Any).downcast_mut::() } /// Consumes `self` to downcast the [`HostError`] into the `T` if possible. /// /// # Errors /// /// If `self` cannot be downcast to `T`. #[inline] pub fn downcast(self: Box) -> Result, Box> { if self.is::() { let Ok(value) = (self as Box).downcast::() else { unreachable!( "failed to downcast `HostError` to T (= {})", type_name::() ); }; Ok(value) } else { Err(self) } } } wasmi_core-1.1.0/src/index_ty.rs000064400000000000000000000022341046102023000147340ustar 00000000000000use crate::ValType; /// The index type used for addressing memories and tables. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum IndexType { /// A 32-bit address type. I32, /// A 64-bit address type. I64, } impl IndexType { /// Returns the [`ValType`] associated to `self`. pub fn ty(&self) -> ValType { match self { IndexType::I32 => ValType::I32, IndexType::I64 => ValType::I64, } } /// Returns `true` if `self` is [`IndexType::I64`]. pub fn is_64(&self) -> bool { matches!(self, Self::I64) } /// Returns the maximum size for Wasm memories and tables for `self`. pub fn max_size(&self) -> u128 { const WASM32_MAX_SIZE: u128 = 1 << 32; const WASM64_MAX_SIZE: u128 = 1 << 64; match self { Self::I32 => WASM32_MAX_SIZE, Self::I64 => WASM64_MAX_SIZE, } } /// Returns the minimum [`IndexType`] between `self` and `other`. pub fn min(&self, other: &Self) -> Self { match (self, other) { (IndexType::I64, IndexType::I64) => IndexType::I64, _ => IndexType::I32, } } } wasmi_core-1.1.0/src/lib.rs000064400000000000000000000025411046102023000136600ustar 00000000000000#![no_std] #![warn( clippy::cast_lossless, clippy::missing_errors_doc, clippy::used_underscore_binding, clippy::redundant_closure_for_method_calls, clippy::type_repetition_in_bounds, clippy::inconsistent_struct_constructor, clippy::default_trait_access, clippy::map_unwrap_or, clippy::items_after_statements )] mod float; mod fuel; mod func_type; mod global; pub mod hint; mod host_error; mod index_ty; mod limiter; mod memory; mod table; mod trap; mod typed; mod untyped; mod value; pub mod wasm; #[cfg(feature = "simd")] pub mod simd; extern crate alloc; #[cfg(feature = "std")] extern crate std; use self::value::{Float, Integer, SignExtendFrom, TruncateSaturateInto, TryTruncateInto}; pub use self::{ float::{F32, F64}, fuel::{Fuel, FuelCosts, FuelCostsProvider, FuelError}, func_type::{FuncType, FuncTypeError}, global::{Global, GlobalError, GlobalType, Mutability}, host_error::HostError, index_ty::IndexType, limiter::{LimiterError, ResourceLimiter, ResourceLimiterRef}, memory::{Memory, MemoryError, MemoryType, MemoryTypeBuilder}, table::{ElementSegment, ElementSegmentRef, Table, TableError, TableType}, trap::{Trap, TrapCode}, typed::{Typed, TypedVal}, untyped::{DecodeUntypedSlice, EncodeUntypedSlice, ReadAs, UntypedError, UntypedVal, WriteAs}, value::{ValType, V128}, }; wasmi_core-1.1.0/src/limiter.rs000064400000000000000000000164221046102023000145620ustar 00000000000000use crate::{MemoryError, TableError}; use core::{ error::Error, fmt, fmt::{Debug, Display}, }; /// An error either returned by a [`ResourceLimiter`] or back to one. #[derive(Debug, Copy, Clone)] pub enum LimiterError { /// Encountered when the underlying system ran out of allocatable memory. OutOfSystemMemory, /// Encountered when a memory or table is grown beyond its bounds. OutOfBoundsGrowth, /// Returned if a [`ResourceLimiter`] denies allocation or growth. ResourceLimiterDeniedAllocation, /// Encountered when an operation ran out of fuel. OutOfFuel { required_fuel: u64 }, } impl Error for LimiterError {} impl Display for LimiterError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let message = match self { LimiterError::OutOfSystemMemory => "out of system memory", LimiterError::OutOfBoundsGrowth => "out of bounds growth", LimiterError::ResourceLimiterDeniedAllocation => "resource limiter denied allocation", LimiterError::OutOfFuel { required_fuel } => { return write!(f, "not enough fuel. required={required_fuel}") } }; write!(f, "{message}") } } impl From for LimiterError { fn from(error: MemoryError) -> Self { match error { MemoryError::OutOfSystemMemory => Self::OutOfSystemMemory, MemoryError::OutOfBoundsGrowth => Self::OutOfBoundsGrowth, MemoryError::ResourceLimiterDeniedAllocation => Self::ResourceLimiterDeniedAllocation, MemoryError::OutOfFuel { required_fuel } => Self::OutOfFuel { required_fuel }, error => panic!("unexpected `MemoryError`: {error}"), } } } impl From for LimiterError { fn from(error: TableError) -> Self { match error { TableError::OutOfSystemMemory => Self::OutOfSystemMemory, TableError::GrowOutOfBounds | TableError::CopyOutOfBounds | TableError::FillOutOfBounds | TableError::InitOutOfBounds => Self::OutOfBoundsGrowth, TableError::ResourceLimiterDeniedAllocation => Self::ResourceLimiterDeniedAllocation, TableError::OutOfFuel { required_fuel } => Self::OutOfFuel { required_fuel }, error => panic!("unexpected `TableError`: {error}"), } } } /// Used by hosts to limit resource consumption of instances. /// /// Resources limited via this trait are primarily related to memory. /// /// Note that this trait does not limit 100% of memory allocated. /// Implementers might still allocate memory to track data structures /// and additionally embedder-specific memory allocations are not /// tracked via this trait. pub trait ResourceLimiter { /// Notifies the resource limiter that an instance's linear memory has been /// requested to grow. /// /// * `current` is the current size of the linear memory in bytes. /// * `desired` is the desired size of the linear memory in bytes. /// * `maximum` is either the linear memory's maximum or a maximum from an /// instance allocator, also in bytes. A value of `None` /// indicates that the linear memory is unbounded. /// /// The `current` and `desired` amounts are guaranteed to always be /// multiples of the WebAssembly page size, 64KiB. /// /// ## Return Value /// /// If `Ok(true)` is returned from this function then the growth operation /// is allowed. This means that the wasm `memory.grow` or `table.grow` instructions /// will return with the `desired` size, in wasm pages. Note that even if /// `Ok(true)` is returned, though, if `desired` exceeds `maximum` then the /// growth operation will still fail. /// /// If `Ok(false)` is returned then this will cause the `grow` instruction /// in a module to return -1 (failure), or in the case of an embedder API /// calling any of the below methods an error will be returned. /// /// - [`Memory::new`] /// - [`Memory::grow`] /// /// # Errors /// /// If `Err(e)` is returned then the `memory.grow` or `table.grow` functions /// will behave as if a trap has been raised. Note that this is not necessarily /// compliant with the WebAssembly specification but it can be a handy and /// useful tool to get a precise backtrace at "what requested so much memory /// to cause a growth failure?". /// /// [`Memory::new`]: crate::Memory::new /// [`Memory::grow`]: crate::Memory::grow fn memory_growing( &mut self, current: usize, desired: usize, maximum: Option, ) -> Result; /// Notifies the resource limiter that an instance's table has been /// requested to grow. /// /// * `current` is the current number of elements in the table. /// * `desired` is the desired number of elements in the table. /// * `maximum` is either the table's maximum or a maximum from an instance /// allocator. A value of `None` indicates that the table is unbounded. /// /// # Errors /// /// See the details on the return values for [`ResourceLimiter::memory_growing`] /// for what the return values of this function indicates. fn table_growing( &mut self, current: usize, desired: usize, maximum: Option, ) -> Result; /// Notifies the resource limiter that growing a memory, permitted by /// the [`ResourceLimiter::memory_growing`] method, has failed. fn memory_grow_failed(&mut self, _error: &LimiterError) {} /// Notifies the resource limiter that growing a table, permitted by /// the [`ResourceLimiter::table_growing`] method, has failed. fn table_grow_failed(&mut self, _error: &LimiterError) {} /// The maximum number of instances that can be created for a Wasm store. /// /// Module instantiation will fail if this limit is exceeded. fn instances(&self) -> usize; /// The maximum number of tables that can be created for a Wasm store. /// /// Creation of tables will fail if this limit is exceeded. fn tables(&self) -> usize; /// The maximum number of linear memories that can be created for a Wasm store. /// /// Creation of memories will fail with an error if this limit is exceeded. fn memories(&self) -> usize; } /// Wrapper around an optional `&mut dyn` [`ResourceLimiter`]. /// /// # Note /// /// This type exists both to make types a little easier to read and to provide /// a `Debug` impl so that `#[derive(Debug)]` works on structs that contain it. #[derive(Default)] pub struct ResourceLimiterRef<'a>(Option<&'a mut dyn ResourceLimiter>); impl Debug for ResourceLimiterRef<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "ResourceLimiterRef(...)") } } impl<'a> From<&'a mut dyn ResourceLimiter> for ResourceLimiterRef<'a> { fn from(limiter: &'a mut dyn ResourceLimiter) -> Self { Self(Some(limiter)) } } impl ResourceLimiterRef<'_> { /// Returns an exclusive reference to the underlying [`ResourceLimiter`] if any. pub fn as_resource_limiter(&mut self) -> Option<&mut dyn ResourceLimiter> { match self.0.as_mut() { Some(limiter) => Some(*limiter), None => None, } } } wasmi_core-1.1.0/src/memory/access.rs000064400000000000000000000171511046102023000156660ustar 00000000000000use crate::TrapCode; /// Convert one type to another by wrapping. pub trait WrapInto { /// Convert one type to another by wrapping. fn wrap_into(self) -> T; } macro_rules! impl_wrap_into { ( $( impl WrapInto<$into:ident> for $from:ident; )* ) => { $( impl WrapInto<$into> for $from { #[inline] fn wrap_into(self) -> $into { self as $into } } )* }; } impl_wrap_into! { impl WrapInto for i32; impl WrapInto for i32; impl WrapInto for i64; impl WrapInto for i64; impl WrapInto for i64; } /// Convert one type to another by extending with leading zeroes. pub trait ExtendInto { /// Convert one type to another by extending with leading zeroes. fn extend_into(self) -> T; } macro_rules! impl_extend_into { ( $( impl ExtendInto<$into:ident> for $from:ident; )* ) => { $( impl ExtendInto<$into> for $from { #[inline] #[allow(clippy::cast_lossless)] fn extend_into(self) -> $into { self as $into } } )* }; } impl_extend_into! { // unsigned -> unsigned impl ExtendInto for u8; impl ExtendInto for u16; impl ExtendInto for u32; // signed -> signed impl ExtendInto for i8; impl ExtendInto for i8; impl ExtendInto for i8; impl ExtendInto for i16; impl ExtendInto for i16; impl ExtendInto for i32; // unsigned -> signed impl ExtendInto for u8; impl ExtendInto for u8; impl ExtendInto for u16; impl ExtendInto for u16; impl ExtendInto for u32; } /// Allows to efficiently load bytes from `memory` into a buffer. pub trait LoadInto { /// Loads bytes from `memory` into `self`. /// /// # Errors /// /// Traps if the `memory` access is out of bounds. fn load_into(&mut self, memory: &[u8], address: usize) -> Result<(), TrapCode>; } impl LoadInto for [u8; N] { #[inline] fn load_into(&mut self, memory: &[u8], address: usize) -> Result<(), TrapCode> { let slice: &Self = memory .get(address..) .and_then(|slice| slice.get(..N)) .and_then(|slice| slice.try_into().ok()) .ok_or(TrapCode::MemoryOutOfBounds)?; *self = *slice; Ok(()) } } /// Allows to efficiently write bytes from a buffer into `memory`. pub trait StoreFrom { /// Writes bytes from `self` to `memory`. /// /// # Errors /// /// Traps if the `memory` access is out of bounds. fn store_from(&self, memory: &mut [u8], address: usize) -> Result<(), TrapCode>; } impl StoreFrom for [u8; N] { #[inline] fn store_from(&self, memory: &mut [u8], address: usize) -> Result<(), TrapCode> { let slice: &mut Self = memory .get_mut(address..) .and_then(|slice| slice.get_mut(..N)) .and_then(|slice| slice.try_into().ok()) .ok_or(TrapCode::MemoryOutOfBounds)?; *slice = *self; Ok(()) } } /// Types that can be converted from and to little endian bytes. pub trait LittleEndianConvert { /// The little endian bytes representation. type Bytes: Default + LoadInto + StoreFrom; /// Converts `self` into little endian bytes. fn into_le_bytes(self) -> Self::Bytes; /// Converts little endian bytes into `Self`. fn from_le_bytes(bytes: Self::Bytes) -> Self; } macro_rules! impl_little_endian_convert_primitive { ( $($primitive:ty),* $(,)? ) => { $( impl LittleEndianConvert for $primitive { type Bytes = [::core::primitive::u8; ::core::mem::size_of::<$primitive>()]; #[inline] fn into_le_bytes(self) -> Self::Bytes { <$primitive>::to_le_bytes(self) } #[inline] fn from_le_bytes(bytes: Self::Bytes) -> Self { <$primitive>::from_le_bytes(bytes) } } )* }; } impl_little_endian_convert_primitive!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, f32, f64); /// Calculates the effective address of a linear memory access. /// /// # Errors /// /// If the resulting effective address overflows. fn effective_address(ptr: u64, offset: u64) -> Result { let Some(address) = ptr.checked_add(offset) else { return Err(TrapCode::MemoryOutOfBounds); }; usize::try_from(address).map_err(|_| TrapCode::MemoryOutOfBounds) } /// Executes a generic `T.load` Wasm operation. /// /// # Errors /// /// - If `ptr + offset` overflows. /// - If `ptr + offset` loads out of bounds from `memory`. pub fn load(memory: &[u8], ptr: u64, offset: u64) -> Result where T: LittleEndianConvert, { let address = effective_address(ptr, offset)?; load_at::(memory, address) } /// Executes a generic `T.load` Wasm operation. /// /// # Errors /// /// If `address` loads out of bounds from `memory`. pub fn load_at(memory: &[u8], address: usize) -> Result where T: LittleEndianConvert, { let mut buffer = <::Bytes as Default>::default(); buffer.load_into(memory, address)?; let value: T = ::from_le_bytes(buffer); Ok(value) } /// Executes a generic `T.loadN_[s|u]` Wasm operation. /// /// # Errors /// /// - If `ptr + offset` overflows. /// - If `ptr + offset` loads out of bounds from `memory`. pub fn load_extend(memory: &[u8], ptr: u64, offset: u64) -> Result where U: LittleEndianConvert + ExtendInto, { let address = effective_address(ptr, offset)?; load_extend_at::(memory, address) } /// Executes a generic `T.loadN_[s|u]` Wasm operation. /// /// # Errors /// /// If `address` loads out of bounds from `memory`. pub fn load_extend_at(memory: &[u8], address: usize) -> Result where U: LittleEndianConvert + ExtendInto, { load_at::(memory, address).map(ExtendInto::extend_into) } /// Executes a generic `T.store` Wasm operation. /// /// # Errors /// /// - If `ptr + offset` overflows. /// - If `ptr + offset` stores out of bounds from `memory`. pub fn store(memory: &mut [u8], ptr: u64, offset: u64, value: T) -> Result<(), TrapCode> where T: LittleEndianConvert, { let address = effective_address(ptr, offset)?; store_at::(memory, address, value) } /// Executes a generic `T.load` Wasm operation. /// /// # Errors /// /// If `address` loads out of bounds from `memory`. pub fn store_at(memory: &mut [u8], address: usize, value: T) -> Result<(), TrapCode> where T: LittleEndianConvert, { let buffer = ::into_le_bytes(value); buffer.store_from(memory, address)?; Ok(()) } /// Executes a generic `T.store[N]` Wasm operation. /// /// # Errors /// /// - If `ptr + offset` overflows. /// - If `ptr + offset` stores out of bounds from `memory`. pub fn store_wrap(memory: &mut [u8], ptr: u64, offset: u64, value: T) -> Result<(), TrapCode> where T: WrapInto, U: LittleEndianConvert, { let address = effective_address(ptr, offset)?; store_wrap_at::(memory, address, value) } /// Executes a generic `T.store[N]` Wasm operation. /// /// # Errors /// /// - If `address` stores out of bounds from `memory`. pub fn store_wrap_at(memory: &mut [u8], address: usize, value: T) -> Result<(), TrapCode> where T: WrapInto, U: LittleEndianConvert, { store_at::(memory, address, value.wrap_into()) } wasmi_core-1.1.0/src/memory/buffer.rs000064400000000000000000000241261046102023000156760ustar 00000000000000use crate::memory::MemoryError; use alloc::{slice, vec::Vec}; use core::{iter, mem::ManuallyDrop}; /// A byte buffer implementation. /// /// # Note /// /// This is less efficient than the byte buffer implementation that is /// based on actual OS provided virtual memory but it is a safe fallback /// solution fitting any platform. #[derive(Debug)] pub struct ByteBuffer { /// The pointer to the underlying byte buffer. pub(super) ptr: *mut u8, /// The current length of the byte buffer. /// /// # Note /// /// - **Vec:** `vec.len()` /// - **Static:** The accessible subslice of the entire underlying static byte buffer. pub(super) len: usize, /// The capacity of the current allocation. /// /// # Note /// /// - **Vec**: `vec.capacity()` /// - **Static:** The total length of the underlying static byte buffer. capacity: usize, /// Whether the [`ByteBuffer`] was initialized from a `&'static [u8]` or a `Vec`. is_static: bool, } // # Safety // // `ByteBuffer` is essentially an `enum`` of `Vec` or `&'static mut [u8]`. // Both of them are `Send` so this is sound. unsafe impl Send for ByteBuffer {} // # Safety // // `ByteBuffer` is essentially an `enum`` of `Vec` or `&'static mut [u8]`. // Both of them are `Sync` so this is sound. unsafe impl Sync for ByteBuffer {} /// Decomposes the `Vec` into its raw components. /// /// Returns the raw pointer to the underlying data, the length of /// the vector (in bytes), and the allocated capacity of the /// data (in bytes). These are the same arguments in the same /// order as the arguments to [`Vec::from_raw_parts`]. /// /// # Safety /// /// After calling this function, the caller is responsible for the /// memory previously managed by the `Vec`. The only way to do /// this is to convert the raw pointer, length, and capacity back /// into a `Vec` with the [`Vec::from_raw_parts`] function, allowing /// the destructor to perform the cleanup. /// /// # Note /// /// This utility method is required since [`Vec::into_raw_parts`] is /// not yet stable unfortunately. (Date: 2024-03-14) fn vec_into_raw_parts(vec: Vec) -> (*mut u8, usize, usize) { let mut vec = ManuallyDrop::new(vec); (vec.as_mut_ptr(), vec.len(), vec.capacity()) } impl ByteBuffer { /// Creates a new byte buffer with the given initial `size` in bytes. /// /// # Errors /// /// If the requested amount of heap bytes could not be allocated. pub fn new(size: usize) -> Result { let mut vec = Vec::new(); if vec.try_reserve(size).is_err() { return Err(MemoryError::OutOfSystemMemory); }; vec.extend(iter::repeat_n(0x00_u8, size)); let (ptr, len, capacity) = vec_into_raw_parts(vec); Ok(Self { ptr, len, capacity, is_static: false, }) } /// Creates a new static byte buffer with the given `size` in bytes. /// /// This will zero all the bytes in `buffer[0..initial_len`]. /// /// # Errors /// /// If `size` is greater than the length of `buffer`. pub fn new_static(buffer: &'static mut [u8], size: usize) -> Result { let Some(bytes) = buffer.get_mut(..size) else { return Err(MemoryError::InvalidStaticBufferSize); }; bytes.fill(0x00_u8); Ok(Self { ptr: buffer.as_mut_ptr(), len: size, capacity: buffer.len(), is_static: true, }) } /// Grows the byte buffer to the given `new_size`. /// /// The newly added bytes will be zero initialized. /// /// # Panics /// /// - If the current size of the [`ByteBuffer`] is larger than `new_size`. /// /// # Errors /// /// - If it is not possible to grow the [`ByteBuffer`] to `new_size`. /// - `vec`: If the system allocator ran out of memory to allocate. /// - `static`: If `new_size` is larger than it's the static buffer capacity. pub fn grow(&mut self, new_size: usize) -> Result<(), MemoryError> { assert!(self.len() <= new_size); match self.get_vec() { Some(vec) => self.grow_vec(vec, new_size), None => self.grow_static(new_size), } } /// Grow the byte buffer to the given `new_size` when backed by a [`Vec`]. fn grow_vec( &mut self, mut vec: ManuallyDrop>, new_size: usize, ) -> Result<(), MemoryError> { debug_assert!(vec.len() <= new_size); let additional = new_size - vec.len(); if vec.try_reserve(additional).is_err() { return Err(MemoryError::OutOfSystemMemory); }; vec.resize(new_size, 0x00_u8); (self.ptr, self.len, self.capacity) = vec_into_raw_parts(ManuallyDrop::into_inner(vec)); Ok(()) } /// Grow the byte buffer to the given `new_size` when backed by a `&'static [u8]`. fn grow_static(&mut self, new_size: usize) -> Result<(), MemoryError> { if self.capacity < new_size { return Err(MemoryError::InvalidStaticBufferSize); } let len = self.len(); self.len = new_size; self.data_mut()[len..new_size].fill(0x00_u8); Ok(()) } /// Returns the length of the byte buffer in bytes. pub fn len(&self) -> usize { self.len } /// Returns a shared slice to the bytes underlying to the byte buffer. pub fn data(&self) -> &[u8] { // # Safety // // The byte buffer is either backed by a `Vec` or a &'static [u8]` // which are both valid byte slices in the range `self.ptr[0..self.len]`. unsafe { slice::from_raw_parts(self.ptr, self.len) } } /// Returns an exclusive slice to the bytes underlying to the byte buffer. pub fn data_mut(&mut self) -> &mut [u8] { // # Safety // // The byte buffer is either backed by a `Vec` or a &'static [u8]` // which are both valid byte slices in the range `self.ptr[0..self.len]`. unsafe { slice::from_raw_parts_mut(self.ptr, self.len) } } /// Returns the underlying `Vec` if the byte buffer is not backed by a static buffer. /// /// Otherwise returns `None`. /// /// # Note /// /// - The returned `Vec` will free its memory and thus the memory of the [`ByteBuffer`] if dropped. /// - The returned `Vec` is returned as [`ManuallyDrop`] to prevent its buffer from being freed /// automatically upon going out of scope. fn get_vec(&mut self) -> Option>> { if self.is_static { return None; } // Safety // // - At this point we are guaranteed that the byte buffer is backed by a `Vec` // so it is safe to reconstruct the `Vec` by its raw parts. // - The returned `Vec` is returned as [`ManuallyDrop`] to prevent its buffer from being free // upon going out of scope. let vec = unsafe { Vec::from_raw_parts(self.ptr, self.len, self.capacity) }; Some(ManuallyDrop::new(vec)) } } impl Drop for ByteBuffer { fn drop(&mut self) { self.get_vec().map(ManuallyDrop::into_inner); } } #[cfg(test)] mod test { use super::*; #[test] fn test_basic_allocation_deallocation() { let buffer = ByteBuffer::new(10).unwrap(); assert_eq!(buffer.len(), 10); // Dropping the buffer should not cause UB. } #[test] fn test_basic_data_manipulation() { let mut buffer = ByteBuffer::new(10).unwrap(); assert_eq!(buffer.len(), 10); let data = buffer.data(); // test we can read the data assert_eq!(data, &[0; 10]); let data = buffer.data_mut(); // test we can take a mutable reference to the data data[4] = 4; // test we can write to the data and it is not UB let data = buffer.data(); // test we can take a new reference to the data assert_eq!(data, &[0, 0, 0, 0, 4, 0, 0, 0, 0, 0]); // test we can read the data // test drop is okay } #[test] fn test_static_buffer_initialization() { static mut BUF: [u8; 10] = [7; 10]; let buf = unsafe { &mut *core::ptr::addr_of_mut!(BUF) }; let mut buffer = ByteBuffer::new_static(buf, 5).unwrap(); assert_eq!(buffer.len(), 5); // Modifying the static buffer through ByteBuffer and checking its content. let data = buffer.data_mut(); data[0] = 1; unsafe { assert_eq!(BUF[0], 1); } } #[test] fn test_growing_buffer() { let mut buffer = ByteBuffer::new(5).unwrap(); buffer.grow(10).unwrap(); assert_eq!(buffer.len(), 10); assert_eq!(buffer.data(), &[0; 10]); } #[test] fn test_growing_static() { static mut BUF: [u8; 10] = [7; 10]; let buf = unsafe { &mut *core::ptr::addr_of_mut!(BUF) }; let mut buffer = ByteBuffer::new_static(buf, 5).unwrap(); assert_eq!(buffer.len(), 5); assert_eq!(buffer.data(), &[0; 5]); buffer.grow(8).unwrap(); assert_eq!(buffer.len(), 8); assert_eq!(buffer.data(), &[0; 8]); buffer.grow(10).unwrap(); assert_eq!(buffer.len(), 10); assert_eq!(buffer.data(), &[0; 10]); } #[test] fn test_static_buffer_overflow() { static mut BUF: [u8; 5] = [7; 5]; let buf = unsafe { &mut *core::ptr::addr_of_mut!(BUF) }; let mut buffer = ByteBuffer::new_static(buf, 5).unwrap(); assert!(buffer.grow(10).is_err()); } #[test] fn out_of_memory_works() { let mut buffer = ByteBuffer::new(0).unwrap(); assert!(matches!( buffer.grow(usize::MAX).unwrap_err(), MemoryError::OutOfSystemMemory )); assert_eq!(buffer.len(), 0); assert_eq!(buffer.data().get(0), None); assert!(buffer.grow(1).is_ok()); assert!(matches!( buffer.grow(usize::MAX).unwrap_err(), MemoryError::OutOfSystemMemory )); assert_eq!(buffer.len(), 1); assert_eq!(buffer.data().get(0), Some(&0x00_u8)); } } wasmi_core-1.1.0/src/memory/error.rs000064400000000000000000000052761046102023000155630ustar 00000000000000use crate::LimiterError; use core::{error::Error, fmt, fmt::Display}; /// An error that may occur upon operating with virtual or linear memory. #[derive(Debug, Copy, Clone)] pub enum MemoryError { /// Tried to allocate more virtual memory than technically possible. OutOfSystemMemory, /// Tried to grow linear memory out of its set bounds. OutOfBoundsGrowth, /// Tried to access linear memory out of bounds. OutOfBoundsAccess, /// Tried to create an invalid linear memory type. InvalidMemoryType, /// Tried to create memory with invalid static buffer size InvalidStaticBufferSize, /// If a resource limiter denied allocation or growth of a linear memory. ResourceLimiterDeniedAllocation, /// The minimum size of the memory type overflows the system index type. MinimumSizeOverflow, /// The maximum size of the memory type overflows the system index type. MaximumSizeOverflow, /// Encountered if a `memory.grow` operation runs out of fuel. OutOfFuel { required_fuel: u64 }, } impl Error for MemoryError {} impl Display for MemoryError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let message = match self { Self::OutOfSystemMemory => { "tried to allocate more virtual memory than available on the system" } Self::OutOfBoundsGrowth => "out of bounds memory growth", Self::OutOfBoundsAccess => "out of bounds memory access", Self::InvalidMemoryType => "tried to create an invalid linear memory type", Self::InvalidStaticBufferSize => "tried to use too small static buffer", Self::ResourceLimiterDeniedAllocation => { "a resource limiter denied to allocate or grow the linear memory" } Self::MinimumSizeOverflow => { "the minimum size of the memory type overflows the system index type" } Self::MaximumSizeOverflow => { "the maximum size of the memory type overflows the system index type" } Self::OutOfFuel { required_fuel } => { return write!(f, "not enough fuel. required={required_fuel}") } }; write!(f, "{message}") } } impl From for MemoryError { fn from(error: LimiterError) -> Self { match error { LimiterError::OutOfSystemMemory => Self::OutOfSystemMemory, LimiterError::OutOfBoundsGrowth => Self::OutOfBoundsGrowth, LimiterError::ResourceLimiterDeniedAllocation => Self::ResourceLimiterDeniedAllocation, LimiterError::OutOfFuel { required_fuel } => Self::OutOfFuel { required_fuel }, } } } wasmi_core-1.1.0/src/memory/mod.rs000064400000000000000000000262061046102023000152050ustar 00000000000000mod access; mod buffer; mod error; mod ty; #[cfg(test)] mod tests; use self::buffer::ByteBuffer; pub use self::{ access::{ load, load_at, load_extend, load_extend_at, store, store_at, store_wrap, store_wrap_at, }, error::MemoryError, ty::{MemoryType, MemoryTypeBuilder}, }; use crate::{Fuel, FuelError, ResourceLimiterRef}; use core::ops::Range; #[cfg(feature = "simd")] pub use self::access::ExtendInto; /// A Wasm linear memory. #[derive(Debug)] pub struct Memory { /// The underlying buffer that stores the bytes of the memory. bytes: ByteBuffer, /// The underlying type of the memory. memory_type: MemoryType, } impl Memory { /// Creates a new [`Memory`] with the given `memory_type`. /// /// # Errors /// /// If creation of the linear memory fails or is disallowed by the `limiter`. pub fn new( memory_type: MemoryType, limiter: &mut ResourceLimiterRef<'_>, ) -> Result { Self::new_impl(memory_type, limiter, ByteBuffer::new) } /// Creates a new static [`Memory`] with the given `memory_type`. /// /// # Note /// /// This uses `buffer` to store its bytes and won't perform heap allocations. /// /// # Errors /// /// If creation of the linear memory fails or is disallowed by the `limiter`. pub fn new_static( memory_type: MemoryType, limiter: &mut ResourceLimiterRef<'_>, buffer: &'static mut [u8], ) -> Result { Self::new_impl(memory_type, limiter, |initial_size| { ByteBuffer::new_static(buffer, initial_size) }) } fn new_impl( memory_type: MemoryType, limiter: &mut ResourceLimiterRef<'_>, make_buffer: impl FnOnce(usize) -> Result, ) -> Result { let Ok(min_size) = memory_type.minimum_byte_size() else { return Err(MemoryError::MinimumSizeOverflow); }; let Ok(min_size) = usize::try_from(min_size) else { return Err(MemoryError::MinimumSizeOverflow); }; let max_size = match memory_type.maximum() { Some(max) => { let max = u128::from(max); if max > memory_type.absolute_max() { return Err(MemoryError::MaximumSizeOverflow); } // Note: We have to clip `max_size` at `usize::MAX` since we do not want to // error if the system limits are overflown here. This is because Wasm // memories grow lazily and thus creation of memories which have a max // size that overflows system limits are valid as long as they do not // grow beyond those limits. let max_size = usize::try_from(max << memory_type.page_size_log2()).unwrap_or(usize::MAX); Some(max_size) } None => None, }; if let Some(limiter) = limiter.as_resource_limiter() { if !limiter.memory_growing(0, min_size, max_size)? { return Err(MemoryError::ResourceLimiterDeniedAllocation); } } let bytes = match make_buffer(min_size) { Ok(buffer) => buffer, Err(error) => { if let Some(limiter) = limiter.as_resource_limiter() { limiter.memory_grow_failed(&error.into()) } return Err(error); } }; Ok(Self { bytes, memory_type }) } /// Returns the memory type of the linear memory. pub fn ty(&self) -> MemoryType { self.memory_type } /// Returns the dynamic [`MemoryType`] of the [`Memory`]. /// /// # Note /// /// This respects the current size of the [`Memory`] as /// its minimum size and is useful for import subtyping checks. pub fn dynamic_ty(&self) -> MemoryType { let current_pages = self.size(); let maximum_pages = self.ty().maximum(); let page_size_log2 = self.ty().page_size_log2(); let is_64 = self.ty().is_64(); let mut b = MemoryType::builder(); b.min(current_pages); b.max(maximum_pages); b.page_size_log2(page_size_log2); b.memory64(is_64); b.build() .expect("must result in valid memory type due to invariants") } /// Returns the size, in WebAssembly pages, of this Wasm linear memory. pub fn size(&self) -> u64 { (self.bytes.len() as u64) >> self.memory_type.page_size_log2() } /// Returns the size of this Wasm linear memory in bytes. fn size_in_bytes(&self) -> u64 { let pages = self.size(); let bytes_per_page = u64::from(self.memory_type.page_size()); let Some(bytes) = pages.checked_mul(bytes_per_page) else { panic!( "unexpected out of bounds linear memory size: \ (pages = {pages}, bytes_per_page = {bytes_per_page})" ) }; bytes } /// Returns the maximum size of this Wasm linear memory in bytes if any. fn max_size_in_bytes(&self) -> Option { let max_pages = self.memory_type.maximum()?; let bytes_per_page = u64::from(self.memory_type.page_size()); let Some(max_bytes) = max_pages.checked_mul(bytes_per_page) else { panic!( "unexpected out of bounds linear memory maximum size: \ (max_pages = {max_pages}, bytes_per_page = {bytes_per_page})" ) }; Some(max_bytes) } /// Grows the linear memory by the given amount of new pages. /// /// Returns the amount of pages before the operation upon success. /// /// # Errors /// /// - If the linear memory cannot be grown to the target size. /// - If the `limiter` denies the growth operation. pub fn grow( &mut self, additional: u64, fuel: Option<&mut Fuel>, limiter: &mut ResourceLimiterRef<'_>, ) -> Result { fn notify_limiter( limiter: &mut ResourceLimiterRef<'_>, err: MemoryError, ) -> Result { if let Some(limiter) = limiter.as_resource_limiter() { limiter.memory_grow_failed(&err.into()) } Err(err) } if additional == 0 { return Ok(self.size()); } let current_byte_size = self.size_in_bytes() as usize; let maximum_byte_size = self.max_size_in_bytes().map(|max| max as usize); let current_size = self.size(); let Some(desired_size) = current_size.checked_add(additional) else { return Err(MemoryError::OutOfBoundsGrowth); }; if u128::from(desired_size) > self.memory_type.absolute_max() { return Err(MemoryError::OutOfBoundsGrowth); } if let Some(maximum_size) = self.memory_type.maximum() { if desired_size > maximum_size { return Err(MemoryError::OutOfBoundsGrowth); } } let bytes_per_page = u64::from(self.memory_type.page_size()); let Some(desired_byte_size) = desired_size.checked_mul(bytes_per_page) else { return Err(MemoryError::OutOfBoundsGrowth); }; let Ok(desired_byte_size) = usize::try_from(desired_byte_size) else { return Err(MemoryError::OutOfBoundsGrowth); }; // The `ResourceLimiter` gets first look at the request. if let Some(limiter) = limiter.as_resource_limiter() { match limiter.memory_growing(current_byte_size, desired_byte_size, maximum_byte_size) { Ok(true) => Ok(()), Ok(false) => Err(MemoryError::OutOfBoundsGrowth), Err(error) => Err(error.into()), }?; } // Optionally check if there is enough fuel for the operation. // // This is deliberately done right before the actual growth operation in order to // not charge fuel if there is any other deterministic failure preventing the expensive // growth operation. if let Some(fuel) = fuel { let additional_bytes = additional .checked_mul(bytes_per_page) .expect("additional size is within [min, max) page bounds"); if let Err(FuelError::OutOfFuel { required_fuel }) = fuel.consume_fuel_if(|costs| costs.fuel_for_copying_bytes(additional_bytes)) { return notify_limiter(limiter, MemoryError::OutOfFuel { required_fuel }); } } // At this point all checks passed to grow the linear memory: // // 1. The resource limiter validated the memory consumption. // 2. The growth is within bounds. // 3. There is enough fuel for the operation. // // Only the actual growing of the underlying byte buffer may now fail. if let Err(error) = self.bytes.grow(desired_byte_size) { return notify_limiter(limiter, error); } Ok(current_size) } /// Returns a shared slice to the bytes underlying to the byte buffer. pub fn data(&self) -> &[u8] { self.bytes.data() } /// Returns an exclusive slice to the bytes underlying to the byte buffer. pub fn data_mut(&mut self) -> &mut [u8] { self.bytes.data_mut() } /// Returns the base pointer, in the host’s address space, that the [`Memory`] is located at. pub fn data_ptr(&self) -> *mut u8 { self.bytes.ptr } /// Returns the byte length of this [`Memory`]. /// /// The returned value will be a multiple of the wasm page size, 64k. pub fn data_size(&self) -> usize { self.bytes.len } /// Returns the index span for the memory access at `start..(start+len)`. fn access_span(start: usize, len: usize) -> Result, MemoryError> { let Some(end) = start.checked_add(len) else { return Err(MemoryError::OutOfBoundsAccess); }; Ok(start..end) } /// Reads `n` bytes from `memory[offset..offset+n]` into `buffer` /// where `n` is the length of `buffer`. /// /// # Errors /// /// If this operation accesses out of bounds linear memory. pub fn read(&self, offset: usize, buffer: &mut [u8]) -> Result<(), MemoryError> { let span = Self::access_span(offset, buffer.len())?; let slice = self .data() .get(span) .ok_or(MemoryError::OutOfBoundsAccess)?; buffer.copy_from_slice(slice); Ok(()) } /// Writes `n` bytes to `memory[offset..offset+n]` from `buffer` /// where `n` if the length of `buffer`. /// /// # Errors /// /// If this operation accesses out of bounds linear memory. pub fn write(&mut self, offset: usize, buffer: &[u8]) -> Result<(), MemoryError> { let span = Self::access_span(offset, buffer.len())?; let slice = self .data_mut() .get_mut(span) .ok_or(MemoryError::OutOfBoundsAccess)?; slice.copy_from_slice(buffer); Ok(()) } } wasmi_core-1.1.0/src/memory/tests.rs000064400000000000000000000013521046102023000155630ustar 00000000000000use super::*; fn memory_type(minimum: u32, maximum: impl Into>) -> MemoryType { let mut b = MemoryType::builder(); b.min(u64::from(minimum)); b.max(maximum.into().map(u64::from)); b.build().unwrap() } #[test] fn subtyping_works() { assert!(memory_type(0, 1).is_subtype_of(&memory_type(0, 1))); assert!(memory_type(0, 1).is_subtype_of(&memory_type(0, 2))); assert!(!memory_type(0, 2).is_subtype_of(&memory_type(0, 1))); assert!(memory_type(2, None).is_subtype_of(&memory_type(1, None))); assert!(memory_type(0, None).is_subtype_of(&memory_type(0, None))); assert!(memory_type(0, 1).is_subtype_of(&memory_type(0, None))); assert!(!memory_type(0, None).is_subtype_of(&memory_type(0, 1))); } wasmi_core-1.1.0/src/memory/ty.rs000064400000000000000000000230361046102023000150600ustar 00000000000000use crate::{IndexType, MemoryError}; /// Internal memory type data and details. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct MemoryTypeInner { /// The initial or minimum amount of pages. minimum: u64, /// The optional maximum amount of pages. maximum: Option, /// The size of a page log2. page_size_log2: u8, /// The index type used to address a linear memory. index_type: IndexType, } /// A type to indicate that a size calculation has overflown. #[derive(Debug, Copy, Clone)] pub struct SizeOverflow; impl MemoryTypeInner { /// Returns the minimum size, in bytes, that the linear memory must have. /// /// # Errors /// /// If the calculation of the minimum size overflows the maximum size. /// This means that the linear memory can't be allocated. /// The caller is responsible to deal with that situation. fn minimum_byte_size(&self) -> Result { let min = u128::from(self.minimum); if min > self.absolute_max() { return Err(SizeOverflow); } Ok(min << self.page_size_log2) } /// Returns the maximum size, in bytes, that the linear memory must have. /// /// # Note /// /// If the maximum size of a memory type is not specified a concrete /// maximum value is returned dependent on the index type of the memory type. /// /// # Errors /// /// If the calculation of the maximum size overflows the index type. /// This means that the linear memory can't be allocated. /// The caller is responsible to deal with that situation. fn maximum_byte_size(&self) -> Result { match self.maximum { Some(max) => { let max = u128::from(max); if max > self.absolute_max() { return Err(SizeOverflow); } Ok(max << self.page_size_log2) } None => Ok(self.max_size_based_on_index_type()), } } /// Returns the size of the linear memory pages in bytes. fn page_size(&self) -> u32 { debug_assert!( self.page_size_log2 == 16 || self.page_size_log2 == 0, "invalid `page_size_log2`: {}; must be 16 or 0", self.page_size_log2 ); 1 << self.page_size_log2 } /// Returns the maximum size in bytes allowed by the `index_type` of this memory type. /// /// # Note /// /// - This does _not_ take into account the page size. /// - This is based _only_ on the index type used by the memory type. fn max_size_based_on_index_type(&self) -> u128 { self.index_type.max_size() } /// Returns the absolute maximum size in pages that a linear memory is allowed to have. fn absolute_max(&self) -> u128 { self.max_size_based_on_index_type() >> self.page_size_log2 } } /// The memory type of a linear memory. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct MemoryType { inner: MemoryTypeInner, } /// A builder for [`MemoryType`]s. /// /// Constructed via [`MemoryType::builder`] or via [`MemoryTypeBuilder::default`]. /// Allows to incrementally build-up a [`MemoryType`]. When done, finalize creation /// via a call to [`MemoryTypeBuilder::build`]. pub struct MemoryTypeBuilder { inner: MemoryTypeInner, } impl Default for MemoryTypeBuilder { fn default() -> Self { Self { inner: MemoryTypeInner { minimum: 0, maximum: None, page_size_log2: MemoryType::DEFAULT_PAGE_SIZE_LOG2, index_type: IndexType::I32, }, } } } impl MemoryTypeBuilder { /// Set whether this is a 64-bit memory type or not. /// /// By default a memory is a 32-bit, a.k.a. `false`. /// /// 64-bit memories are part of the [Wasm `memory64` proposal]. /// /// [Wasm `memory64` proposal]: https://github.com/WebAssembly/memory64 pub fn memory64(&mut self, memory64: bool) -> &mut Self { self.inner.index_type = match memory64 { true => IndexType::I64, false => IndexType::I32, }; self } /// Sets the minimum number of pages the built [`MemoryType`] supports. /// /// The default minimum is `0`. pub fn min(&mut self, minimum: u64) -> &mut Self { self.inner.minimum = minimum; self } /// Sets the optional maximum number of pages the built [`MemoryType`] supports. /// /// A value of `None` means that there is no maximum number of pages. /// /// The default maximum is `None`. pub fn max(&mut self, maximum: Option) -> &mut Self { self.inner.maximum = maximum; self } /// Sets the log2 page size in bytes, for the built [`MemoryType`]. /// /// The default value is 16, which results in the default Wasm page size of 64KiB (aka 2^16 or 65536). /// /// Currently, the only allowed values are 0 (page size of 1) or 16 (the default). /// Future Wasm proposal extensions might change this limitation. /// /// Non-default page sizes are part of the [`custom-page-sizes proposal`] /// for WebAssembly which is not fully standardized yet. /// /// [`custom-page-sizes proposal`]: https://github.com/WebAssembly/custom-page-sizes pub fn page_size_log2(&mut self, page_size_log2: u8) -> &mut Self { self.inner.page_size_log2 = page_size_log2; self } /// Finalize the construction of the [`MemoryType`]. /// /// # Errors /// /// If the chosen configuration for the constructed [`MemoryType`] is invalid. pub fn build(self) -> Result { self.validate()?; Ok(MemoryType { inner: self.inner }) } /// Validates the configured [`MemoryType`] of the [`MemoryTypeBuilder`]. /// /// # Errors /// /// If the chosen configuration for the constructed [`MemoryType`] is invalid. fn validate(&self) -> Result<(), MemoryError> { match self.inner.page_size_log2 { 0 | MemoryType::DEFAULT_PAGE_SIZE_LOG2 => {} _ => { // Case: currently, pages sizes log2 can only be 0 or 16. // Note: Future Wasm extensions might allow more values. return Err(MemoryError::InvalidMemoryType); } } if self.inner.minimum_byte_size().is_err() { // Case: the minimum size overflows a `absolute_max` return Err(MemoryError::InvalidMemoryType); } if let Some(max) = self.inner.maximum { if self.inner.maximum_byte_size().is_err() { // Case: the maximum size overflows a `absolute_max` return Err(MemoryError::InvalidMemoryType); } if self.inner.minimum > max { // Case: maximum size must be at least as large as minimum size return Err(MemoryError::InvalidMemoryType); } } Ok(()) } } impl MemoryType { /// The default memory page size in KiB. const DEFAULT_PAGE_SIZE_LOG2: u8 = 16; // 2^16 KiB = 64 KiB /// Returns a [`MemoryTypeBuilder`] to incrementally construct a [`MemoryType`]. pub fn builder() -> MemoryTypeBuilder { MemoryTypeBuilder::default() } /// Returns `true` if this is a 64-bit [`MemoryType`]. /// /// 64-bit memories are part of the Wasm `memory64` proposal. pub fn is_64(&self) -> bool { self.index_ty().is_64() } /// Returns the [`IndexType`] used by the [`MemoryType`]. pub fn index_ty(&self) -> IndexType { self.inner.index_type } /// Returns the minimum pages of the memory type. pub fn minimum(self) -> u64 { self.inner.minimum } /// Returns the maximum pages of the memory type. /// /// Returns `None` if there is no limit set. pub fn maximum(self) -> Option { self.inner.maximum } /// Returns the page size of the [`MemoryType`] in bytes. pub fn page_size(self) -> u32 { self.inner.page_size() } /// Returns the page size of the [`MemoryType`] in log2(bytes). pub fn page_size_log2(self) -> u8 { self.inner.page_size_log2 } /// Returns the minimum size, in bytes, that the linear memory must have. /// /// # Errors /// /// If the calculation of the minimum size overflows the maximum size. /// This means that the linear memory can't be allocated. /// The caller is responsible to deal with that situation. pub(crate) fn minimum_byte_size(self) -> Result { self.inner.minimum_byte_size() } /// Returns the absolute maximum size in pages that a linear memory is allowed to have. pub(crate) fn absolute_max(&self) -> u128 { self.inner.absolute_max() } /// Returns `true` if the [`MemoryType`] is a subtype of the `other` [`MemoryType`]. /// /// # Note /// /// This implements the [subtyping rules] according to the WebAssembly spec. /// /// [import subtyping]: /// https://webassembly.github.io/spec/core/valid/types.html#import-subtyping pub fn is_subtype_of(&self, other: &Self) -> bool { if self.is_64() != other.is_64() { return false; } if self.page_size() != other.page_size() { return false; } if self.minimum() < other.minimum() { return false; } match (self.maximum(), other.maximum()) { (_, None) => true, (Some(max), Some(other_max)) => max <= other_max, _ => false, } } } wasmi_core-1.1.0/src/simd.rs000064400000000000000000002063221046102023000140510ustar 00000000000000//! Defines the entire Wasm `simd` proposal API. use crate::{ memory::{self, ExtendInto}, simd, value::Float, wasm, TrapCode, V128, }; use core::{ array, ops::{BitAnd, BitOr, BitXor, Neg, Not}, }; macro_rules! op { ($ty:ty, $op:tt) => {{ |lhs: $ty, rhs: $ty| lhs $op rhs }}; } /// An error that may occur when constructing an out of bounds lane index. pub struct OutOfBoundsLaneIdx; /// Helper trait used to infer the [`ImmLaneIdx`] from a given primitive. pub trait IntoLaneIdx { /// The associated lane index type. type LaneIdx: Sized + Copy + TryFrom + Into; } macro_rules! impl_into_lane_idx { ( $( impl IntoLaneIdx for $ty:ty = $lane_idx:ty; )* ) => { $( impl IntoLaneIdx for $ty { type LaneIdx = $lane_idx; } )* }; } impl_into_lane_idx! { impl IntoLaneIdx for i8 = ImmLaneIdx<16>; impl IntoLaneIdx for u8 = ImmLaneIdx<16>; impl IntoLaneIdx for i16 = ImmLaneIdx<8>; impl IntoLaneIdx for u16 = ImmLaneIdx<8>; impl IntoLaneIdx for i32 = ImmLaneIdx<4>; impl IntoLaneIdx for u32 = ImmLaneIdx<4>; impl IntoLaneIdx for f32 = ImmLaneIdx<4>; impl IntoLaneIdx for i64 = ImmLaneIdx<2>; impl IntoLaneIdx for u64 = ImmLaneIdx<2>; impl IntoLaneIdx for f64 = ImmLaneIdx<2>; } /// A byte with values in the range 0–N identifying a lane. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct ImmLaneIdx(u8); impl ImmLaneIdx { /// Helper bit mask for construction and getter. const MASK: u8 = (1_u8 << u8::ilog2(N)) - 1; fn zero() -> Self { Self(0) } } impl From> for u8 { fn from(lane: ImmLaneIdx) -> u8 { lane.0 & >::MASK } } impl TryFrom for ImmLaneIdx { type Error = OutOfBoundsLaneIdx; fn try_from(lane: u8) -> Result { if lane > Self::MASK { return Err(OutOfBoundsLaneIdx); } Ok(Self(lane)) } } /// A byte with values in the range 0–1 identifying a lane. pub type ImmLaneIdx2 = ImmLaneIdx<2>; /// A byte with values in the range 0–3 identifying a lane. pub type ImmLaneIdx4 = ImmLaneIdx<4>; /// A byte with values in the range 0–7 identifying a lane. pub type ImmLaneIdx8 = ImmLaneIdx<8>; /// A byte with values in the range 0–15 identifying a lane. pub type ImmLaneIdx16 = ImmLaneIdx<16>; /// A byte with values in the range 0–31 identifying a lane. pub type ImmLaneIdx32 = ImmLaneIdx<32>; /// Internal helper trait to help the type inference to do its jobs with fewer type annotations. /// /// # Note /// /// - This trait and its applications are hidden from outside this module. /// - For example `i32` is associated to the `i32x4` lane type. trait IntoLanes { /// The `Lanes` type associated to the implementing type. type Lanes: Lanes; /// The `LaneIdx` type associated to the implementing type. type LaneIdx; } /// Internal helper trait implemented by `Lanes` types. /// /// Possible `Lanes` types include: /// /// - `I64x2` /// - `I32x4` /// - `I16x8` /// - `I8x16` /// - `F64x2` /// - `F32x4` trait Lanes { /// The type used in the lanes. E.g. `i32` for `i32x4`. type Item; /// The associated lane index type. E.g. `ImmLaneIdx4` for `i32x4`. type LaneIdx; /// The number of lanes for `Self`. const LANES: usize; /// A lane item where all bits are `1`. const ALL_ONES: Self::Item; /// A lane item where all bits are `0`. const ALL_ZEROS: Self::Item; /// Converts the [`V128`] to `Self`. fn from_v128(value: V128) -> Self; /// Converts `self` to a [`V128`] value. fn into_v128(self) -> V128; /// Creates `Self` by splatting `value`. fn splat(value: Self::Item) -> Self; /// Extract the item at `lane` from `self`. fn extract_lane(self, lane: Self::LaneIdx) -> Self::Item; /// Replace the item at `lane` with `item` and return `self` afterwards. fn replace_lane(self, lane: Self::LaneIdx, item: Self::Item) -> Self; /// Apply `f` for all lane items in `self`. fn lanewise_unary(self, f: impl Fn(Self::Item) -> Self::Item) -> Self; /// Apply `f` for all pairs of lane items in `self` and `other`. fn lanewise_binary(self, other: Self, f: impl Fn(Self::Item, Self::Item) -> Self::Item) -> Self; /// Apply `f` for all triplets of lane items in `self` and `other`. fn lanewise_ternary( self, b: Self, c: Self, f: impl Fn(Self::Item, Self::Item, Self::Item) -> Self::Item, ) -> Self; /// Apply `f` comparison for all pairs of lane items in `self` and `other`. /// /// Storing [`Self::ALL_ONES`] if `f` evaluates to `true` or [`Self::ALL_ZEROS`] otherwise per item. fn lanewise_comparison(self, other: Self, f: impl Fn(Self::Item, Self::Item) -> bool) -> Self; /// Apply `f(i, n, acc)` for all lane items `i` at pos `n` in `self` and return the result. fn lanewise_reduce(self, acc: T, f: impl Fn(u8, Self::Item, T) -> T) -> T; } macro_rules! impl_lanes_for { ( $( $( #[$attr:meta] )* struct $name:ident([$ty:ty; $n:literal]); )* ) => { $( $( #[$attr] )* #[derive(Copy, Clone)] #[repr(transparent)] struct $name([$ty; $n]); impl IntoLanes for $ty { type Lanes = $name; type LaneIdx = ImmLaneIdx<$n>; } impl From<[$ty; $n]> for $name { fn from(array: [$ty; $n]) -> Self { Self(array) } } impl Lanes for $name { type Item = $ty; type LaneIdx = ImmLaneIdx<$n>; const LANES: usize = $n; const ALL_ONES: Self::Item = <$ty>::from_le_bytes([0xFF_u8; 16 / $n]); const ALL_ZEROS: Self::Item = <$ty>::from_le_bytes([0x00_u8; 16 / $n]); fn from_v128(value: V128) -> Self { // SAFETY: the types chosen to implement `Split` are always // of same size as `V128` and have no invalid bit-patterns. // // Note: it is important to state that this could be implemented // in safe Rust entirely. However, during development it turned // out that _not_ using unsafe transmutation confused the // optimizer enough that optimizations became very flaky. // This was tested across a variety of compiler versions. Self(unsafe { ::core::mem::transmute::(value) }) } fn into_v128(self) -> V128 { // SAFETY: the types chosen to implement `Combine` are always // of same size as `V128` and have no invalid bit-patterns. // // Note: see note from `from_v128` method above. unsafe { ::core::mem::transmute::<[$ty; $n], V128>(self.0) } } fn splat(value: Self::Item) -> Self { Self([value; $n]) } fn extract_lane(self, lane: Self::LaneIdx) -> Self::Item { self.0[u8::from(lane) as usize] } fn replace_lane(self, lane: Self::LaneIdx, item: Self::Item) -> Self { let mut this = self; this.0[u8::from(lane) as usize] = item; this } fn lanewise_unary(self, f: impl Fn(Self::Item) -> Self::Item) -> Self { let mut this = self.0; for i in 0..Self::LANES { this[i] = f(this[i]); } Self(this) } fn lanewise_binary(self, other: Self, f: impl Fn(Self::Item, Self::Item) -> Self::Item) -> Self { let mut lhs = self.0; let rhs = other.0; for i in 0..Self::LANES { lhs[i] = f(lhs[i], rhs[i]); } Self(lhs) } fn lanewise_ternary(self, b: Self, c: Self, f: impl Fn(Self::Item, Self::Item, Self::Item) -> Self::Item) -> Self { let mut a = self.0; let b = b.0; let c = c.0; for i in 0..Self::LANES { a[i] = f(a[i], b[i], c[i]); } Self(a) } fn lanewise_comparison(self, other: Self, f: impl Fn(Self::Item, Self::Item) -> bool) -> Self { self.lanewise_binary(other, |lhs, rhs| match f(lhs, rhs) { true => Self::ALL_ONES, false => Self::ALL_ZEROS, }) } fn lanewise_reduce(self, acc: T, f: impl Fn(u8, Self::Item, T) -> T) -> T { let this = self.0; let mut acc = acc; for i in 0..Self::LANES { acc = f(i as u8, this[i], acc); } acc } } )* }; } impl_lanes_for! { /// The Wasm `i64x2` vector type consisting of 2 `i64` values. struct I64x2([i64; 2]); /// The Wasm `u64x2` vector type consisting of 2 `u64` values. struct U64x2([u64; 2]); /// The Wasm `i32x4` vector type consisting of 4 `i32` values. struct I32x4([i32; 4]); /// The Wasm `u32x4` vector type consisting of 4 `u32` values. struct U32x4([u32; 4]); /// The Wasm `i16x8` vector type consisting of 8 `i16` values. struct I16x8([i16; 8]); /// The Wasm `u16x8` vector type consisting of 8 `u16` values. struct U16x8([u16; 8]); /// The Wasm `i8x16` vector type consisting of 16 `i8` values. struct I8x16([i8; 16]); /// The Wasm `u8x16` vector type consisting of 16 `u8` values. struct U8x16([u8; 16]); /// The Wasm `f32x4` vector type consisting of 4 `f32` values. struct F32x4([f32; 4]); /// The Wasm `f64x2` vector type consisting of 2 `f64` values. struct F64x2([f64; 2]); } /// `Self` can be constructed from the narrower lanes. /// /// For example a `i64x2` vector can be constructed from the two lower lanes of a `i32x4`. trait FromNarrow: Lanes { /// Construct `Self` from the pairwise application of `f` of items in `narrow`. fn pairwise_unary( narrow: NarrowLanes, f: impl Fn(NarrowLanes::Item, NarrowLanes::Item) -> Self::Item, ) -> Self; /// Construct `Self` from the pairwise application of `f` of items in `lhs` and `rhs`. fn pairwise_binary( lhs: NarrowLanes, rhs: NarrowLanes, f: impl Fn([NarrowLanes::Item; 2], [NarrowLanes::Item; 2]) -> Self::Item, ) -> Self; /// Construct `Self` from the application of `f` to the lower half lanes of `narrow`. fn low_unary(narrow: NarrowLanes, f: impl Fn(NarrowLanes::Item) -> Self::Item) -> Self; /// Construct `Self` from the application of `f` to the higher half lanes of `narrow`. fn high_unary(narrow: NarrowLanes, f: impl Fn(NarrowLanes::Item) -> Self::Item) -> Self; /// Construct `Self` from the binary application of `f` to the lower half lanes of `narrow_lhs` and `narrow_rhs`. fn low_binary( narrow_lhs: NarrowLanes, narrow_rhs: NarrowLanes, f: impl Fn(NarrowLanes::Item, NarrowLanes::Item) -> Self::Item, ) -> Self; /// Construct `Self` from the binary application of `f` to the higher half lanes of `narrow_lhs` and `narrow_rhs`. fn high_binary( narrow_lhs: NarrowLanes, narrow_rhs: NarrowLanes, f: impl Fn(NarrowLanes::Item, NarrowLanes::Item) -> Self::Item, ) -> Self; } macro_rules! impl_from_narrow_for { ( $( impl FromNarrow<$narrow_ty:ty> for $self_ty:ty; )* ) => { $( impl FromNarrow<$narrow_ty> for $self_ty { fn pairwise_unary( narrow: $narrow_ty, f: impl Fn(<$narrow_ty as Lanes>::Item, <$narrow_ty as Lanes>::Item) -> Self::Item, ) -> Self { let narrow = narrow.0; Self(array::from_fn(|i| f(narrow[2 * i], narrow[2 * i + 1]))) } fn pairwise_binary( lhs: $narrow_ty, rhs: $narrow_ty, f: impl Fn([<$narrow_ty as Lanes>::Item; 2], [<$narrow_ty as Lanes>::Item; 2]) -> Self::Item, ) -> Self { let lhs = lhs.0; let rhs = rhs.0; Self(array::from_fn(|i| { f( [lhs[2 * i], lhs[2 * i + 1]], [rhs[2 * i], rhs[2 * i + 1]], ) })) } fn low_unary(narrow: $narrow_ty, f: impl Fn(<$narrow_ty as Lanes>::Item) -> Self::Item) -> Self { Self(array::from_fn(|i| f(narrow.0[i]))) } fn high_unary(narrow: $narrow_ty, f: impl Fn(<$narrow_ty as Lanes>::Item) -> Self::Item) -> Self { Self(array::from_fn(|i| f(narrow.0[i + Self::LANES]))) } fn low_binary( narrow_lhs: $narrow_ty, narrow_rhs: $narrow_ty, f: impl Fn(<$narrow_ty as Lanes>::Item, <$narrow_ty as Lanes>::Item) -> Self::Item, ) -> Self { let narrow_lhs = narrow_lhs.0; let narrow_rhs = narrow_rhs.0; Self(array::from_fn(|i| f(narrow_lhs[i], narrow_rhs[i]))) } fn high_binary( narrow_lhs: $narrow_ty, narrow_rhs: $narrow_ty, f: impl Fn(<$narrow_ty as Lanes>::Item, <$narrow_ty as Lanes>::Item) -> Self::Item, ) -> Self { let narrow_lhs = narrow_lhs.0; let narrow_rhs = narrow_rhs.0; Self(array::from_fn(|i| { f(narrow_lhs[i + Self::LANES], narrow_rhs[i + Self::LANES]) })) } } )* }; } impl_from_narrow_for! { impl FromNarrow for I64x2; impl FromNarrow for U64x2; impl FromNarrow for I32x4; impl FromNarrow for U32x4; impl FromNarrow for I16x8; impl FromNarrow for U16x8; impl FromNarrow for I64x2; impl FromNarrow for U64x2; impl FromNarrow for F64x2; impl FromNarrow for F64x2; impl FromNarrow for F64x2; } /// `Self` can be constructed from the wider lanes. /// /// For example a `i32x4` vector can be constructed from a `i64x2`. trait FromWide: Lanes { /// Construct `Self` from the application of `f` to the wide `low` and `high` items. fn from_low_high( low: WideLanes, high: WideLanes, f: impl Fn(WideLanes::Item) -> Self::Item, ) -> Self; /// Construct `Self` from the application of `f` to the wide `low` or evaluate `high`. fn from_low_or( low: WideLanes, high: impl Fn() -> Self::Item, f: impl Fn(WideLanes::Item) -> Self::Item, ) -> Self; } macro_rules! impl_from_wide_for { ( $( impl FromWide<$wide_ty:ty> for $narrow_ty:ty; )* ) => { $( impl FromWide<$wide_ty> for $narrow_ty { fn from_low_high( low: $wide_ty, high: $wide_ty, f: impl Fn(<$wide_ty as Lanes>::Item) -> Self::Item, ) -> Self { let low = low.0; let high = high.0; Self(array::from_fn(|i| { match i < <$wide_ty as Lanes>::LANES { true => f(low[i]), false => f(high[i - <$wide_ty as Lanes>::LANES]), } })) } fn from_low_or( low: $wide_ty, high: impl Fn() -> Self::Item, f: impl Fn(<$wide_ty as Lanes>::Item) -> Self::Item, ) -> Self { let low = low.0; Self(array::from_fn(|i| { match i < <$wide_ty as Lanes>::LANES { true => f(low[i]), false => high(), } })) } } )* }; } impl_from_wide_for! { impl FromWide for I32x4; impl FromWide for U32x4; impl FromWide for F32x4; impl FromWide for I16x8; impl FromWide for U16x8; impl FromWide for I8x16; impl FromWide for U8x16; } trait ReinterpretAs { fn reinterpret_as(self) -> T; } macro_rules! impl_reinterpret_as_for { ( $ty0:ty, $ty1:ty ) => { impl ReinterpretAs<$ty0> for $ty1 { fn reinterpret_as(self) -> $ty0 { <$ty0>::from_ne_bytes(self.to_ne_bytes()) } } impl ReinterpretAs<$ty1> for $ty0 { fn reinterpret_as(self) -> $ty1 { <$ty1>::from_ne_bytes(self.to_ne_bytes()) } } }; } impl_reinterpret_as_for!(i32, f32); impl_reinterpret_as_for!(u32, f32); impl_reinterpret_as_for!(i64, f64); impl_reinterpret_as_for!(u64, f64); impl V128 { /// Convenience method to help implement splatting methods. fn splat(value: T) -> Self { <::Lanes>::splat(value).into_v128() } /// Convenience method to help implement lane extraction methods. fn extract_lane(self, lane: ::LaneIdx) -> T { <::Lanes>::from_v128(self).extract_lane(lane) } /// Convenience method to help implement lane replacement methods. fn replace_lane(self, lane: ::LaneIdx, item: T) -> Self { <::Lanes>::from_v128(self) .replace_lane(lane, item) .into_v128() } /// Convenience method to help implement lanewise unary methods. fn lanewise_unary(self, f: impl Fn(T) -> T) -> Self { <::Lanes>::from_v128(self) .lanewise_unary(f) .into_v128() } /// Convenience method to help implement lanewise unary cast methods. fn lanewise_unary_cast(self, f: impl Fn(T) -> U) -> Self where U: ReinterpretAs, { <::Lanes>::from_v128(self) .lanewise_unary(|v| f(v).reinterpret_as()) .into_v128() } /// Convenience method to help implement lanewise binary methods. fn lanewise_binary(lhs: Self, rhs: Self, f: impl Fn(T, T) -> T) -> Self { let lhs = <::Lanes>::from_v128(lhs); let rhs = <::Lanes>::from_v128(rhs); lhs.lanewise_binary(rhs, f).into_v128() } /// Convenience method to help implement lanewise ternary methods. fn lanewise_ternary(a: Self, b: Self, c: Self, f: impl Fn(T, T, T) -> T) -> Self { let a = <::Lanes>::from_v128(a); let b = <::Lanes>::from_v128(b); let c = <::Lanes>::from_v128(c); a.lanewise_ternary(b, c, f).into_v128() } /// Convenience method to help implement lanewise comparison methods. fn lanewise_comparison(lhs: Self, rhs: Self, f: impl Fn(T, T) -> bool) -> Self { let lhs = <::Lanes>::from_v128(lhs); let rhs = <::Lanes>::from_v128(rhs); lhs.lanewise_comparison(rhs, f).into_v128() } /// Convenience method to help implement lanewise reduce methods. fn lanewise_reduce(self, acc: V, f: impl Fn(T, V) -> V) -> V { self.lanewise_reduce_enumerate::(acc, |_, v: T, acc: V| f(v, acc)) } /// Convenience method to help implement lanewise reduce methods with a loop-index. fn lanewise_reduce_enumerate(self, acc: V, f: impl Fn(u8, T, V) -> V) -> V { <::Lanes>::from_v128(self).lanewise_reduce(acc, f) } /// Convenience method to help implement pairwise unary methods. fn pairwise_unary( self, f: impl Fn(Narrow, Narrow) -> Wide, ) -> Self where ::Lanes: FromNarrow<::Lanes>, { <::Lanes as FromNarrow<::Lanes>>::pairwise_unary( <::Lanes>::from_v128(self), f, ) .into_v128() } /// Convenience method to help implement pairwise binary methods. fn pairwise_binary( lhs: Self, rhs: Self, f: impl Fn([Narrow; 2], [Narrow; 2]) -> Wide, ) -> Self where ::Lanes: FromNarrow<::Lanes>, { <::Lanes as FromNarrow<::Lanes>>::pairwise_binary( <::Lanes>::from_v128(lhs), <::Lanes>::from_v128(rhs), f, ) .into_v128() } /// Convenience method to help implement extend-low unary methods. fn low_unary(self, f: impl Fn(Narrow) -> Wide) -> Self where ::Lanes: FromNarrow<::Lanes>, { <::Lanes as FromNarrow<::Lanes>>::low_unary( <::Lanes>::from_v128(self), f, ) .into_v128() } /// Convenience method to help implement extend-high unary methods. fn high_unary(self, f: impl Fn(Narrow) -> Wide) -> Self where ::Lanes: FromNarrow<::Lanes>, { <::Lanes as FromNarrow<::Lanes>>::high_unary( <::Lanes>::from_v128(self), f, ) .into_v128() } /// Convenience method to help implement extend-low binary methods. fn from_low_binary( lhs: Self, rhs: Self, f: impl Fn(Narrow, Narrow) -> Wide, ) -> Self where ::Lanes: FromNarrow<::Lanes>, { <::Lanes as FromNarrow<::Lanes>>::low_binary( <::Lanes>::from_v128(lhs), <::Lanes>::from_v128(rhs), f, ) .into_v128() } /// Convenience method to help implement extend-high binary methods. fn from_high_binary( lhs: Self, rhs: Self, f: impl Fn(Narrow, Narrow) -> Wide, ) -> Self where ::Lanes: FromNarrow<::Lanes>, { <::Lanes as FromNarrow<::Lanes>>::high_binary( <::Lanes>::from_v128(lhs), <::Lanes>::from_v128(rhs), f, ) .into_v128() } /// Convenience method to help implement narrowing low-high methods. fn from_low_high( lhs: Self, rhs: Self, f: impl Fn(Wide) -> Narrow, ) -> Self where ::Lanes: FromWide<::Lanes>, { <::Lanes as FromWide<::Lanes>>::from_low_high( <::Lanes>::from_v128(lhs), <::Lanes>::from_v128(rhs), f, ) .into_v128() } /// Convenience method to help implement narrowing low-or methods. fn low_or( self, high: impl Fn() -> Narrow, f: impl Fn(Wide) -> Narrow, ) -> Self where ::Lanes: FromWide<::Lanes>, { <::Lanes as FromWide<::Lanes>>::from_low_or( <::Lanes>::from_v128(self), high, f, ) .into_v128() } } /// Concenience identity helper function. fn identity(x: T) -> T { x } macro_rules! impl_splat_for { ( $( fn $name:ident(value: $ty:ty) -> V128; )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($name), "` instruction.")] pub fn $name(value: $ty) -> V128 { V128::splat(value) } )* }; } impl_splat_for! { fn i64x2_splat(value: i64) -> V128; fn i32x4_splat(value: i32) -> V128; fn i16x8_splat(value: i16) -> V128; fn i8x16_splat(value: i8) -> V128; fn f32x4_splat(value: f32) -> V128; fn f64x2_splat(value: f64) -> V128; } macro_rules! impl_extract_for { ( $( fn $name:ident(v128: V128, lane: $lane_ty:ty) -> $ret_ty:ty = $convert:expr; )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($name), "` instruction.")] pub fn $name(v128: V128, lane: $lane_ty) -> $ret_ty { ($convert)(v128.extract_lane(lane)) } )* }; } impl_extract_for! { fn i64x2_extract_lane(v128: V128, lane: ImmLaneIdx2) -> i64 = identity; fn i32x4_extract_lane(v128: V128, lane: ImmLaneIdx4) -> i32 = identity; fn f64x2_extract_lane(v128: V128, lane: ImmLaneIdx2) -> f64 = identity; fn f32x4_extract_lane(v128: V128, lane: ImmLaneIdx4) -> f32 = identity; fn i8x16_extract_lane_s(v128: V128, lane: ImmLaneIdx16) -> i32 = >::into; fn i8x16_extract_lane_u(v128: V128, lane: ImmLaneIdx16) -> u32 = >::into; fn i16x8_extract_lane_s(v128: V128, lane: ImmLaneIdx8) -> i32 = >::into; fn i16x8_extract_lane_u(v128: V128, lane: ImmLaneIdx8) -> u32 = >::into; } macro_rules! impl_replace_for { ( $( fn $name:ident(v128: V128, lane: $lane_ty:ty, item: $item_ty:ty) -> V128; )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($name), "` instruction.")] pub fn $name(v128: V128, lane: $lane_ty, item: $item_ty) -> V128 { v128.replace_lane(lane, item) } )* }; } impl_replace_for! { fn i64x2_replace_lane(v128: V128, lane: ImmLaneIdx2, item: i64) -> V128; fn i32x4_replace_lane(v128: V128, lane: ImmLaneIdx4, item: i32) -> V128; fn i16x8_replace_lane(v128: V128, lane: ImmLaneIdx8, item: i16) -> V128; fn i8x16_replace_lane(v128: V128, lane: ImmLaneIdx16, item: i8) -> V128; fn f64x2_replace_lane(v128: V128, lane: ImmLaneIdx2, item: f64) -> V128; fn f32x4_replace_lane(v128: V128, lane: ImmLaneIdx4, item: f32) -> V128; } /// Executes a Wasm `i8x16.shuffle` instruction. pub fn i8x16_shuffle(a: V128, b: V128, s: [ImmLaneIdx32; 16]) -> V128 { let a = I8x16::from_v128(a).0; let b = I8x16::from_v128(b).0; I8x16(array::from_fn(|i| match usize::from(u8::from(s[i])) { i @ 0..16 => a[i], i => b[i - 16], })) .into_v128() } /// Executes a Wasm `i8x16.swizzle` instruction. pub fn i8x16_swizzle(a: V128, s: V128) -> V128 { let a = U8x16::from_v128(a).0; let s = U8x16::from_v128(s).0; U8x16(array::from_fn(|i| match usize::from(s[i]) { i @ 0..16 => a[i], _ => 0, })) .into_v128() } macro_rules! impl_unary_for { ( $( fn $name:ident(v128: V128) -> V128 = $lanewise_expr:expr; )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($name), "` instruction.")] pub fn $name(v128: V128) -> V128 { V128::lanewise_unary(v128, $lanewise_expr) } )* }; } macro_rules! impl_unary_cast_for { ( $( fn $name:ident(v128: V128) -> V128 = $lanewise_expr:expr; )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($name), "` instruction.")] pub fn $name(v128: V128) -> V128 { V128::lanewise_unary_cast(v128, $lanewise_expr) } )* }; } /// Lanewise operation for the Wasm `q15mulr_sat` SIMD operation. fn i16x8_q15mulr_sat(x: i16, y: i16) -> i16 { const MIN: i32 = i16::MIN as i32; const MAX: i32 = i16::MAX as i32; let x = i32::from(x); let y = i32::from(y); let q15mulr = (x * y + (1 << 14)) >> 15; q15mulr.clamp(MIN, MAX) as i16 } macro_rules! avgr { ($ty:ty as $wide_ty:ty) => {{ |a: $ty, b: $ty| { let a = <$wide_ty as ::core::convert::From<$ty>>::from(a); let b = <$wide_ty as ::core::convert::From<$ty>>::from(b); a.wrapping_add(b).div_ceil(2) as $ty } }}; } /// Wasm SIMD `pmin` (pseudo-min) definition. fn pmin(lhs: T, rhs: T) -> T { if rhs < lhs { rhs } else { lhs } } /// Wasm SIMD `pmax` (pseudo-max) definition. fn pmax(lhs: T, rhs: T) -> T { if lhs < rhs { rhs } else { lhs } } macro_rules! impl_binary_for { ( $( fn $name:ident(lhs: V128, rhs: V128) -> V128 = $lanewise_expr:expr; )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($name), "` instruction.")] pub fn $name(lhs: V128, rhs: V128) -> V128 { V128::lanewise_binary(lhs, rhs, $lanewise_expr) } )* }; } impl_binary_for! { fn i64x2_add(lhs: V128, rhs: V128) -> V128 = i64::wrapping_add; fn i32x4_add(lhs: V128, rhs: V128) -> V128 = i32::wrapping_add; fn i16x8_add(lhs: V128, rhs: V128) -> V128 = i16::wrapping_add; fn i8x16_add(lhs: V128, rhs: V128) -> V128 = i8::wrapping_add; fn i64x2_sub(lhs: V128, rhs: V128) -> V128 = i64::wrapping_sub; fn i32x4_sub(lhs: V128, rhs: V128) -> V128 = i32::wrapping_sub; fn i16x8_sub(lhs: V128, rhs: V128) -> V128 = i16::wrapping_sub; fn i8x16_sub(lhs: V128, rhs: V128) -> V128 = i8::wrapping_sub; fn i64x2_mul(lhs: V128, rhs: V128) -> V128 = i64::wrapping_mul; fn i32x4_mul(lhs: V128, rhs: V128) -> V128 = i32::wrapping_mul; fn i16x8_mul(lhs: V128, rhs: V128) -> V128 = i16::wrapping_mul; fn i8x16_mul(lhs: V128, rhs: V128) -> V128 = i8::wrapping_mul; fn i8x16_add_sat_s(lhs: V128, rhs: V128) -> V128 = i8::saturating_add; fn i8x16_add_sat_u(lhs: V128, rhs: V128) -> V128 = u8::saturating_add; fn i16x8_add_sat_s(lhs: V128, rhs: V128) -> V128 = i16::saturating_add; fn i16x8_add_sat_u(lhs: V128, rhs: V128) -> V128 = u16::saturating_add; fn i8x16_sub_sat_s(lhs: V128, rhs: V128) -> V128 = i8::saturating_sub; fn i8x16_sub_sat_u(lhs: V128, rhs: V128) -> V128 = u8::saturating_sub; fn i16x8_sub_sat_s(lhs: V128, rhs: V128) -> V128 = i16::saturating_sub; fn i16x8_sub_sat_u(lhs: V128, rhs: V128) -> V128 = u16::saturating_sub; fn i16x8_q15mulr_sat_s(lhs: V128, rhs: V128) -> V128 = i16x8_q15mulr_sat; fn i8x16_min_s(lhs: V128, rhs: V128) -> V128 = i8::min; fn i8x16_min_u(lhs: V128, rhs: V128) -> V128 = u8::min; fn i16x8_min_s(lhs: V128, rhs: V128) -> V128 = i16::min; fn i16x8_min_u(lhs: V128, rhs: V128) -> V128 = u16::min; fn i32x4_min_s(lhs: V128, rhs: V128) -> V128 = i32::min; fn i32x4_min_u(lhs: V128, rhs: V128) -> V128 = u32::min; fn i8x16_max_s(lhs: V128, rhs: V128) -> V128 = i8::max; fn i8x16_max_u(lhs: V128, rhs: V128) -> V128 = u8::max; fn i16x8_max_s(lhs: V128, rhs: V128) -> V128 = i16::max; fn i16x8_max_u(lhs: V128, rhs: V128) -> V128 = u16::max; fn i32x4_max_s(lhs: V128, rhs: V128) -> V128 = i32::max; fn i32x4_max_u(lhs: V128, rhs: V128) -> V128 = u32::max; fn i8x16_avgr_u(lhs: V128, rhs: V128) -> V128 = avgr!(u8 as u16); fn i16x8_avgr_u(lhs: V128, rhs: V128) -> V128 = avgr!(u16 as u32); fn v128_and(lhs: V128, rhs: V128) -> V128 = ::bitand; fn v128_or(lhs: V128, rhs: V128) -> V128 = ::bitor; fn v128_xor(lhs: V128, rhs: V128) -> V128 = ::bitxor; fn v128_andnot(lhs: V128, rhs: V128) -> V128 = |a: u64, b: u64| a & !b; fn f32x4_min(lhs: V128, rhs: V128) -> V128 = wasm::f32_min; fn f64x2_min(lhs: V128, rhs: V128) -> V128 = wasm::f64_min; fn f32x4_max(lhs: V128, rhs: V128) -> V128 = wasm::f32_max; fn f64x2_max(lhs: V128, rhs: V128) -> V128 = wasm::f64_max; fn f32x4_pmin(lhs: V128, rhs: V128) -> V128 = pmin::; fn f64x2_pmin(lhs: V128, rhs: V128) -> V128 = pmin::; fn f32x4_pmax(lhs: V128, rhs: V128) -> V128 = pmax::; fn f64x2_pmax(lhs: V128, rhs: V128) -> V128 = pmax::; fn f32x4_add(lhs: V128, rhs: V128) -> V128 = op!(f32, +); fn f64x2_add(lhs: V128, rhs: V128) -> V128 = op!(f64, +); fn f32x4_sub(lhs: V128, rhs: V128) -> V128 = op!(f32, -); fn f64x2_sub(lhs: V128, rhs: V128) -> V128 = op!(f64, -); fn f32x4_div(lhs: V128, rhs: V128) -> V128 = op!(f32, /); fn f64x2_div(lhs: V128, rhs: V128) -> V128 = op!(f64, /); fn f32x4_mul(lhs: V128, rhs: V128) -> V128 = op!(f32, *); fn f64x2_mul(lhs: V128, rhs: V128) -> V128 = op!(f64, *); } impl_unary_for! { fn i64x2_neg(v128: V128) -> V128 = i64::wrapping_neg; fn i32x4_neg(v128: V128) -> V128 = i32::wrapping_neg; fn i16x8_neg(v128: V128) -> V128 = i16::wrapping_neg; fn i8x16_neg(v128: V128) -> V128 = i8::wrapping_neg; fn i8x16_abs(v128: V128) -> V128 = i8::wrapping_abs; fn i16x8_abs(v128: V128) -> V128 = i16::wrapping_abs; fn i32x4_abs(v128: V128) -> V128 = i32::wrapping_abs; fn i64x2_abs(v128: V128) -> V128 = i64::wrapping_abs; fn v128_not(v128: V128) -> V128 = ::not; fn i8x16_popcnt(v128: V128) -> V128 = |v: u8| v.count_ones() as u8; fn f32x4_neg(v128: V128) -> V128 = ::neg; fn f64x2_neg(v128: V128) -> V128 = ::neg; fn f32x4_abs(v128: V128) -> V128 = f32::abs; fn f64x2_abs(v128: V128) -> V128 = f64::abs; fn f32x4_sqrt(v128: V128) -> V128 = wasm::f32_sqrt; fn f64x2_sqrt(v128: V128) -> V128 = wasm::f64_sqrt; fn f32x4_ceil(v128: V128) -> V128 = wasm::f32_ceil; fn f64x2_ceil(v128: V128) -> V128 = wasm::f64_ceil; fn f32x4_floor(v128: V128) -> V128 = wasm::f32_floor; fn f64x2_floor(v128: V128) -> V128 = wasm::f64_floor; fn f32x4_trunc(v128: V128) -> V128 = wasm::f32_trunc; fn f64x2_trunc(v128: V128) -> V128 = wasm::f64_trunc; fn f32x4_nearest(v128: V128) -> V128 = wasm::f32_nearest; fn f64x2_nearest(v128: V128) -> V128 = wasm::f64_nearest; } impl_unary_cast_for! { fn f32x4_convert_i32x4_s(v128: V128) -> V128 = wasm::f32_convert_i32_s; fn f32x4_convert_i32x4_u(v128: V128) -> V128 = wasm::f32_convert_i32_u; fn i32x4_trunc_sat_f32x4_s(v128: V128) -> V128 = wasm::i32_trunc_sat_f32_s; fn i32x4_trunc_sat_f32x4_u(v128: V128) -> V128 = wasm::i32_trunc_sat_f32_u; } macro_rules! impl_comparison_for { ( $( fn $name:ident(lhs: V128, rhs: V128) -> V128 = $lanewise_expr:expr; )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($name), "` instruction.")] pub fn $name(lhs: V128, rhs: V128) -> V128 { V128::lanewise_comparison(lhs, rhs, $lanewise_expr) } )* }; } impl_comparison_for! { fn i8x16_eq(lhs: V128, rhs: V128) -> V128 = op!(i8, ==); fn i16x8_eq(lhs: V128, rhs: V128) -> V128 = op!(i16, ==); fn i32x4_eq(lhs: V128, rhs: V128) -> V128 = op!(i32, ==); fn i64x2_eq(lhs: V128, rhs: V128) -> V128 = op!(i64, ==); fn f32x4_eq(lhs: V128, rhs: V128) -> V128 = op!(f32, ==); fn f64x2_eq(lhs: V128, rhs: V128) -> V128 = op!(f64, ==); fn i8x16_ne(lhs: V128, rhs: V128) -> V128 = op!(i8, !=); fn i16x8_ne(lhs: V128, rhs: V128) -> V128 = op!(i16, !=); fn i32x4_ne(lhs: V128, rhs: V128) -> V128 = op!(i32, !=); fn i64x2_ne(lhs: V128, rhs: V128) -> V128 = op!(i64, !=); fn f32x4_ne(lhs: V128, rhs: V128) -> V128 = op!(f32, !=); fn f64x2_ne(lhs: V128, rhs: V128) -> V128 = op!(f64, !=); fn i8x16_lt_s(lhs: V128, rhs: V128) -> V128 = op!(i8, <); fn i8x16_lt_u(lhs: V128, rhs: V128) -> V128 = op!(u8, <); fn i16x8_lt_s(lhs: V128, rhs: V128) -> V128 = op!(i16, <); fn i16x8_lt_u(lhs: V128, rhs: V128) -> V128 = op!(u16, <); fn i32x4_lt_s(lhs: V128, rhs: V128) -> V128 = op!(i32, <); fn i32x4_lt_u(lhs: V128, rhs: V128) -> V128 = op!(u32, <); fn i64x2_lt_s(lhs: V128, rhs: V128) -> V128 = op!(i64, <); fn f32x4_lt(lhs: V128, rhs: V128) -> V128 = op!(f32, <); fn f64x2_lt(lhs: V128, rhs: V128) -> V128 = op!(f64, <); fn i8x16_le_s(lhs: V128, rhs: V128) -> V128 = op!(i8, <=); fn i8x16_le_u(lhs: V128, rhs: V128) -> V128 = op!(u8, <=); fn i16x8_le_s(lhs: V128, rhs: V128) -> V128 = op!(i16, <=); fn i16x8_le_u(lhs: V128, rhs: V128) -> V128 = op!(u16, <=); fn i32x4_le_s(lhs: V128, rhs: V128) -> V128 = op!(i32, <=); fn i32x4_le_u(lhs: V128, rhs: V128) -> V128 = op!(u32, <=); fn i64x2_le_s(lhs: V128, rhs: V128) -> V128 = op!(i64, <=); fn f32x4_le(lhs: V128, rhs: V128) -> V128 = op!(f32, <=); fn f64x2_le(lhs: V128, rhs: V128) -> V128 = op!(f64, <=); fn i8x16_gt_s(lhs: V128, rhs: V128) -> V128 = op!(i8, >); fn i8x16_gt_u(lhs: V128, rhs: V128) -> V128 = op!(u8, >); fn i16x8_gt_s(lhs: V128, rhs: V128) -> V128 = op!(i16, >); fn i16x8_gt_u(lhs: V128, rhs: V128) -> V128 = op!(u16, >); fn i32x4_gt_s(lhs: V128, rhs: V128) -> V128 = op!(i32, >); fn i32x4_gt_u(lhs: V128, rhs: V128) -> V128 = op!(u32, >); fn i64x2_gt_s(lhs: V128, rhs: V128) -> V128 = op!(i64, >); fn f32x4_gt(lhs: V128, rhs: V128) -> V128 = op!(f32, >); fn f64x2_gt(lhs: V128, rhs: V128) -> V128 = op!(f64, >); fn i8x16_ge_s(lhs: V128, rhs: V128) -> V128 = op!(i8, >=); fn i8x16_ge_u(lhs: V128, rhs: V128) -> V128 = op!(u8, >=); fn i16x8_ge_s(lhs: V128, rhs: V128) -> V128 = op!(i16, >=); fn i16x8_ge_u(lhs: V128, rhs: V128) -> V128 = op!(u16, >=); fn i32x4_ge_s(lhs: V128, rhs: V128) -> V128 = op!(i32, >=); fn i32x4_ge_u(lhs: V128, rhs: V128) -> V128 = op!(u32, >=); fn i64x2_ge_s(lhs: V128, rhs: V128) -> V128 = op!(i64, >=); fn f32x4_ge(lhs: V128, rhs: V128) -> V128 = op!(f32, >=); fn f64x2_ge(lhs: V128, rhs: V128) -> V128 = op!(f64, >=); } macro_rules! impl_widen_low_unary { ( $( fn $name:ident(v128: V128) -> V128 = $convert:expr; )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($name), "` instruction.")] pub fn $name(v128: V128) -> V128 { v128.low_unary($convert) } )* }; } impl_widen_low_unary! { fn i16x8_extend_low_i8x16_s(v128: V128) -> V128 = >::into; fn i16x8_extend_low_i8x16_u(v128: V128) -> V128 = >::into; fn i32x4_extend_low_i16x8_s(v128: V128) -> V128 = >::into; fn i32x4_extend_low_i16x8_u(v128: V128) -> V128 = >::into; fn i64x2_extend_low_i32x4_s(v128: V128) -> V128 = >::into; fn i64x2_extend_low_i32x4_u(v128: V128) -> V128 = >::into; fn f64x2_convert_low_i32x4_s(v128: V128) -> V128 = wasm::f64_convert_i32_s; fn f64x2_convert_low_i32x4_u(v128: V128) -> V128 = wasm::f64_convert_i32_u; fn f64x2_promote_low_f32x4(v128: V128) -> V128 = wasm::f64_promote_f32; } macro_rules! impl_widen_high_unary { ( $( fn $name:ident(v128: V128) -> V128 = $convert:expr; )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($name), "` instruction.")] pub fn $name(v128: V128) -> V128 { v128.high_unary($convert) } )* }; } impl_widen_high_unary! { fn i16x8_extend_high_i8x16_s(v128: V128) -> V128 = >::into; fn i16x8_extend_high_i8x16_u(v128: V128) -> V128 = >::into; fn i32x4_extend_high_i16x8_s(v128: V128) -> V128 = >::into; fn i32x4_extend_high_i16x8_u(v128: V128) -> V128 = >::into; fn i64x2_extend_high_i32x4_s(v128: V128) -> V128 = >::into; fn i64x2_extend_high_i32x4_u(v128: V128) -> V128 = >::into; } macro_rules! extmul { ($narrow:ty => $wide:ty) => {{ |a: $narrow, b: $narrow| -> $wide { let a = <$wide as From<$narrow>>::from(a); let b = <$wide as From<$narrow>>::from(b); a.wrapping_mul(b) } }}; } macro_rules! impl_ext_binary_low { ( $( fn $name:ident(lhs: V128, rhs: V128) -> V128 = $f:expr; )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($extmul_low), "` instruction.")] pub fn $name(lhs: V128, rhs: V128) -> V128 { V128::from_low_binary(lhs, rhs, $f) } )* }; } impl_ext_binary_low! { fn i16x8_extmul_low_i8x16_s(lhs: V128, rhs: V128) -> V128 = extmul!( i8 => i16); fn i16x8_extmul_low_i8x16_u(lhs: V128, rhs: V128) -> V128 = extmul!( u8 => u16); fn i32x4_extmul_low_i16x8_s(lhs: V128, rhs: V128) -> V128 = extmul!(i16 => i32); fn i32x4_extmul_low_i16x8_u(lhs: V128, rhs: V128) -> V128 = extmul!(u16 => u32); fn i64x2_extmul_low_i32x4_s(lhs: V128, rhs: V128) -> V128 = extmul!(i32 => i64); fn i64x2_extmul_low_i32x4_u(lhs: V128, rhs: V128) -> V128 = extmul!(u32 => u64); } macro_rules! impl_ext_binary_high { ( $( fn $name:ident(lhs: V128, rhs: V128) -> V128 = $f:expr; )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($extmul_low), "` instruction.")] pub fn $name(lhs: V128, rhs: V128) -> V128 { V128::from_high_binary(lhs, rhs, $f) } )* }; } impl_ext_binary_high! { fn i16x8_extmul_high_i8x16_s(lhs: V128, rhs: V128) -> V128 = extmul!( i8 => i16); fn i16x8_extmul_high_i8x16_u(lhs: V128, rhs: V128) -> V128 = extmul!( u8 => u16); fn i32x4_extmul_high_i16x8_s(lhs: V128, rhs: V128) -> V128 = extmul!(i16 => i32); fn i32x4_extmul_high_i16x8_u(lhs: V128, rhs: V128) -> V128 = extmul!(u16 => u32); fn i64x2_extmul_high_i32x4_s(lhs: V128, rhs: V128) -> V128 = extmul!(i32 => i64); fn i64x2_extmul_high_i32x4_u(lhs: V128, rhs: V128) -> V128 = extmul!(u32 => u64); } macro_rules! impl_extadd_pairwise { ( $( fn $name:ident(v128: V128) -> V128 = $narrow:ty => $wide:ty; )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($name), "` instruction.")] pub fn $name(v128: V128) -> V128 { fn extadd_pairwise(a: $narrow, b: $narrow) -> $wide { let a = <$wide>::from(a); let b = <$wide>::from(b); a.wrapping_add(b) } v128.pairwise_unary(extadd_pairwise) } )* }; } impl_extadd_pairwise! { fn i16x8_extadd_pairwise_i8x16_s(v128: V128) -> V128 = i8 => i16; fn i16x8_extadd_pairwise_i8x16_u(v128: V128) -> V128 = u8 => u16; fn i32x4_extadd_pairwise_i16x8_s(v128: V128) -> V128 = i16 => i32; fn i32x4_extadd_pairwise_i16x8_u(v128: V128) -> V128 = u16 => u32; } macro_rules! impl_shift_ops { ( $( fn $name:ident(v128: V128, rhs: u32) -> V128 = $lanewise_expr:expr; )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($name), "` instruction.")] pub fn $name(v128: V128, rhs: u32) -> V128 { v128.lanewise_unary(|v| $lanewise_expr(v, rhs)) } )* }; } impl_shift_ops! { fn i8x16_shl(v128: V128, rhs: u32) -> V128 = i8::wrapping_shl; fn i16x8_shl(v128: V128, rhs: u32) -> V128 = i16::wrapping_shl; fn i32x4_shl(v128: V128, rhs: u32) -> V128 = i32::wrapping_shl; fn i64x2_shl(v128: V128, rhs: u32) -> V128 = i64::wrapping_shl; fn i8x16_shr_s(v128: V128, rhs: u32) -> V128 = i8::wrapping_shr; fn i8x16_shr_u(v128: V128, rhs: u32) -> V128 = u8::wrapping_shr; fn i16x8_shr_s(v128: V128, rhs: u32) -> V128 = i16::wrapping_shr; fn i16x8_shr_u(v128: V128, rhs: u32) -> V128 = u16::wrapping_shr; fn i32x4_shr_s(v128: V128, rhs: u32) -> V128 = i32::wrapping_shr; fn i32x4_shr_u(v128: V128, rhs: u32) -> V128 = u32::wrapping_shr; fn i64x2_shr_s(v128: V128, rhs: u32) -> V128 = i64::wrapping_shr; fn i64x2_shr_u(v128: V128, rhs: u32) -> V128 = u64::wrapping_shr; } macro_rules! impl_narrowing_low_high_ops { ( $( fn $name:ident(low: V128, high: V128) -> V128 = $f:expr; )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($name), "` instruction.")] pub fn $name(low: V128, high: V128) -> V128 { V128::from_low_high(low, high, $f) } )* }; } impl_narrowing_low_high_ops! { fn i8x16_narrow_i16x8_s(low: V128, high: V128) -> V128 = narrow_i16_to_i8; fn i8x16_narrow_i16x8_u(low: V128, high: V128) -> V128 = narrow_u16_to_u8; fn i16x8_narrow_i32x4_s(low: V128, high: V128) -> V128 = narrow_i32_to_i16; fn i16x8_narrow_i32x4_u(low: V128, high: V128) -> V128 = narrow_u32_to_u16; } macro_rules! def_narrow_from_to { ( $( fn $name:ident(value: $from:ty $(as $as:ty)? ) -> $to:ty );* $(;)? ) => { $( #[doc = concat!("Narrows `value` from type `", stringify!($from), "` to type `", stringify!($to), "`.")] fn $name(value: $from) -> $to { $( let value: $as = value as $as; )? value.clamp(<$to>::MIN.into(), <$to>::MAX.into()) as $to } )* }; } def_narrow_from_to! { fn narrow_i16_to_i8(value: i16) -> i8; fn narrow_u16_to_u8(value: u16 as i16) -> u8; fn narrow_i32_to_i16(value: i32) -> i16; fn narrow_u32_to_u16(value: u32 as i32) -> u16; } macro_rules! impl_narrowing_low_high_ops { ( $( fn $name:ident(v128: V128) -> V128 = (high: $high:expr, f: $f:expr); )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($name), "` instruction.")] pub fn $name(low: V128) -> V128 { V128::low_or(low, $high, $f) } )* }; } impl_narrowing_low_high_ops! { fn i32x4_trunc_sat_f64x2_s_zero(v128: V128) -> V128 = (high: || 0, f: wasm::i32_trunc_sat_f64_s); fn i32x4_trunc_sat_f64x2_u_zero(v128: V128) -> V128 = (high: || 0, f: wasm::i32_trunc_sat_f64_u); fn f32x4_demote_f64x2_zero(v128: V128) -> V128 = (high: || 0.0, f: wasm::f32_demote_f64); } macro_rules! all_true { ($ty:ty) => {{ |v: $ty, acc: bool| acc & (v != 0) }}; } macro_rules! impl_all_true_ops { ( $( fn $name:ident(v128: V128) -> bool = $f:expr; )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($name), "` instruction.")] pub fn $name(v128: V128) -> bool { v128.lanewise_reduce(true, $f) } )* }; } impl_all_true_ops! { fn i8x16_all_true(v128: V128) -> bool = all_true!(i8); fn i16x8_all_true(v128: V128) -> bool = all_true!(i16); fn i32x4_all_true(v128: V128) -> bool = all_true!(i32); fn i64x2_all_true(v128: V128) -> bool = all_true!(i64); } macro_rules! bitmask { ($ty:ty) => {{ |n: u8, v: $ty, acc| acc | (i32::from(v < 0).wrapping_shl(u32::from(n))) }}; } macro_rules! impl_bitmask_ops { ( $( fn $name:ident(v128: V128) -> u32 = $f:expr; )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($name), "` instruction.")] pub fn $name(v128: V128) -> u32 { v128.lanewise_reduce_enumerate(0_i32, $f) as _ } )* }; } impl_bitmask_ops! { fn i8x16_bitmask(v128: V128) -> u32 = bitmask!(i8); fn i16x8_bitmask(v128: V128) -> u32 = bitmask!(i16); fn i32x4_bitmask(v128: V128) -> u32 = bitmask!(i32); fn i64x2_bitmask(v128: V128) -> u32 = bitmask!(i64); } /// Executes a Wasm `v128.any_true` instruction. pub fn v128_any_true(v128: V128) -> bool { v128.as_u128() != 0 } /// Executes a Wasm `i32x4.dot_i16x8_s` instruction. pub fn i32x4_dot_i16x8_s(lhs: V128, rhs: V128) -> V128 { fn dot(a: [i16; 2], b: [i16; 2]) -> i32 { let a = a.map(i32::from); let b = b.map(i32::from); let dot0 = a[0].wrapping_mul(b[0]); let dot1 = a[1].wrapping_mul(b[1]); dot0.wrapping_add(dot1) } V128::pairwise_binary(lhs, rhs, dot) } /// Executes a Wasm `i16x8.relaxed_dot_i8x16_i7x16_s` instruction. /// /// # Note /// /// This is part of the `relaxed-simd` Wasm proposal. pub fn i16x8_relaxed_dot_i8x16_i7x16_s(lhs: V128, rhs: V128) -> V128 { fn dot(a: [i8; 2], b: [i8; 2]) -> i16 { let a = a.map(i16::from); let b = b.map(i16::from); let dot0 = a[0].wrapping_mul(b[0]); let dot1 = a[1].wrapping_mul(b[1]); dot0.wrapping_add(dot1) } V128::pairwise_binary(lhs, rhs, dot) } /// Executes a Wasm `i32x4.relaxed_dot_i8x16_i7x16_add_s` instruction. /// /// # Note /// /// This is part of the `relaxed-simd` Wasm proposal. pub fn i32x4_relaxed_dot_i8x16_i7x16_add_s(lhs: V128, rhs: V128, c: V128) -> V128 { let dot = i16x8_relaxed_dot_i8x16_i7x16_s(lhs, rhs); let ext = i32x4_extadd_pairwise_i16x8_s(dot); i32x4_add(ext, c) } /// Executes a Wasm `v128.bitselect` instruction. pub fn v128_bitselect(v1: V128, v2: V128, c: V128) -> V128 { simd::v128_or(simd::v128_and(v1, c), simd::v128_andnot(v2, c)) } /// Computes the negative `mul_add`: `-(a * b) + c` fn neg_mul_add(a: T, b: T, c: T) -> T where T: Float + Neg, { ::mul_add(a.neg(), b, c) } macro_rules! impl_ternary_for { ( $( fn $name:ident(a: V128, b: V128, c: V128) -> V128 = $lanewise_expr:expr; )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($name), "` instruction.")] #[doc = ""] #[doc = "# Note"] #[doc = ""] #[doc = "This is part of the `relaxed-simd` Wasm proposal."] pub fn $name(a: V128, b: V128, c: V128) -> V128 { V128::lanewise_ternary(a, b, c, $lanewise_expr) } )* }; } impl_ternary_for! { fn f32x4_relaxed_madd(a: V128, b: V128, c: V128) -> V128 = ::mul_add; fn f32x4_relaxed_nmadd(a: V128, b: V128, c: V128) -> V128 = neg_mul_add::; fn f64x2_relaxed_madd(a: V128, b: V128, c: V128) -> V128 = ::mul_add; fn f64x2_relaxed_nmadd(a: V128, b: V128, c: V128) -> V128 = neg_mul_add::; } /// Executes a Wasm `v128.store` instruction. /// /// # Errors /// /// - If `ptr + offset` overflows. /// - If `ptr + offset` stores out of bounds from `memory`. pub fn v128_store(memory: &mut [u8], ptr: u64, offset: u64, value: V128) -> Result<(), TrapCode> { memory::store(memory, ptr, offset, value.as_u128()) } /// Executes a Wasm `v128.store` instruction. /// /// # Errors /// /// If `address` stores out of bounds from `memory`. pub fn v128_store_at(memory: &mut [u8], address: usize, value: V128) -> Result<(), TrapCode> { memory::store_at(memory, address, value.as_u128()) } macro_rules! impl_v128_storeN_lane { ( $( fn $name:ident( memory: &mut [u8], ptr: u64, offset: u64, value: V128, imm: $lane_idx:ty $(,)? ) -> Result<(), TrapCode> = $store_ty:ty; )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($name), "` instruction.")] /// /// # Errors /// /// - If `ptr + offset` overflows. /// - If `ptr + offset` stores out of bounds from `memory`. pub fn $name(memory: &mut [u8], ptr: u64, offset: u64, value: V128, imm: $lane_idx) -> Result<(), TrapCode> { memory::store(memory, ptr, offset, value.extract_lane::<$store_ty>(imm)) } )* }; } impl_v128_storeN_lane! { fn v128_store8_lane( memory: &mut [u8], ptr: u64, offset: u64, value: V128, imm: ImmLaneIdx16, ) -> Result<(), TrapCode> = u8; fn v128_store16_lane( memory: &mut [u8], ptr: u64, offset: u64, value: V128, imm: ImmLaneIdx8, ) -> Result<(), TrapCode> = u16; fn v128_store32_lane( memory: &mut [u8], ptr: u64, offset: u64, value: V128, imm: ImmLaneIdx4, ) -> Result<(), TrapCode> = u32; fn v128_store64_lane( memory: &mut [u8], ptr: u64, offset: u64, value: V128, imm: ImmLaneIdx2, ) -> Result<(), TrapCode> = u64; } macro_rules! impl_v128_storeN_lane_at { ( $( fn $name:ident(memory: &mut [u8], address: usize, value: V128, imm: $lane_idx:ty) -> Result<(), TrapCode> = $store_ty:ty; )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($name), "` instruction.")] /// /// # Errors /// /// If `address` stores out of bounds from `memory`. pub fn $name(memory: &mut [u8], address: usize, value: V128, imm: $lane_idx) -> Result<(), TrapCode> { memory::store_at(memory, address, value.extract_lane::<$store_ty>(imm)) } )* }; } impl_v128_storeN_lane_at! { fn v128_store8_lane_at( memory: &mut [u8], address: usize, value: V128, imm: ImmLaneIdx16 ) -> Result<(), TrapCode> = u8; fn v128_store16_lane_at( memory: &mut [u8], address: usize, value: V128, imm: ImmLaneIdx8 ) -> Result<(), TrapCode> = u16; fn v128_store32_lane_at( memory: &mut [u8], address: usize, value: V128, imm: ImmLaneIdx4 ) -> Result<(), TrapCode> = u32; fn v128_store64_lane_at( memory: &mut [u8], address: usize, value: V128, imm: ImmLaneIdx2 ) -> Result<(), TrapCode> = u64; } /// Executes a Wasmi `v128.load` instruction. /// /// # Errors /// /// - If `ptr + offset` overflows. /// - If `ptr + offset` loads out of bounds from `memory`. pub fn v128_load(memory: &[u8], ptr: u64, offset: u64) -> Result { memory::load::(memory, ptr, offset).map(V128::from) } /// Executes a Wasmi `v128.load` instruction. /// /// # Errors /// /// If `address` loads out of bounds from `memory`. pub fn v128_load_at(memory: &[u8], address: usize) -> Result { memory::load_at::(memory, address).map(V128::from) } macro_rules! impl_v128_loadN_zero_for { ( $( fn $name:ident(memory: &[u8], ptr: u64, offset: u64) -> Result = $ty:ty; )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($name), "` instruction.")] /// /// # Errors /// /// - If `ptr + offset` overflows. /// - If `ptr + offset` loads out of bounds from `memory`. pub fn $name(memory: &[u8], ptr: u64, offset: u64) -> Result { let bits = memory::load::<$ty>(memory, ptr, offset)?; Ok(V128::splat::<$ty>(0).replace_lane::<$ty>(<$ty as IntoLaneIdx>::LaneIdx::zero(), bits)) } )* }; } impl_v128_loadN_zero_for! { fn v128_load32_zero(memory: &[u8], ptr: u64, offset: u64) -> Result = u32; fn v128_load64_zero(memory: &[u8], ptr: u64, offset: u64) -> Result = u64; } macro_rules! impl_v128_loadN_zero_at_for { ( $( fn $name:ident(memory: &[u8], address: usize) -> Result = $ty:ty; )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($name), "` instruction.")] /// /// # Errors /// /// If `address` loads out of bounds from `memory`. pub fn $name(memory: &[u8], address: usize) -> Result { let bits = memory::load_at::<$ty>(memory, address)?; Ok(V128::splat::<$ty>(0).replace_lane::<$ty>(<$ty as IntoLaneIdx>::LaneIdx::zero(), bits)) } )* }; } impl_v128_loadN_zero_at_for! { fn v128_load32_zero_at(memory: &[u8], address: usize) -> Result = u32; fn v128_load64_zero_at(memory: &[u8], address: usize) -> Result = u64; } macro_rules! impl_v128_loadN_splat_for { ( $( fn $name:ident(memory: &[u8], ptr: u64, offset: u64) -> Result = $ty:ty; )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($name), "` instruction.")] /// /// # Errors /// /// - If `ptr + offset` overflows. /// - If `ptr + offset` loads out of bounds from `memory`. pub fn $name(memory: &[u8], ptr: u64, offset: u64) -> Result { memory::load::<$ty>(memory, ptr, offset).map(V128::splat) } )* }; } impl_v128_loadN_splat_for! { fn v128_load8_splat(memory: &[u8], ptr: u64, offset: u64) -> Result = u8; fn v128_load16_splat(memory: &[u8], ptr: u64, offset: u64) -> Result = u16; fn v128_load32_splat(memory: &[u8], ptr: u64, offset: u64) -> Result = u32; fn v128_load64_splat(memory: &[u8], ptr: u64, offset: u64) -> Result = u64; } macro_rules! impl_v128_loadN_splat_at_for { ( $( fn $name:ident(memory: &[u8], address: usize) -> Result = $ty:ty; )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($name), "` instruction.")] /// /// # Errors /// /// If `address` loads out of bounds from `memory`. pub fn $name(memory: &[u8], address: usize) -> Result { memory::load_at::<$ty>(memory, address).map(V128::splat) } )* }; } impl_v128_loadN_splat_at_for! { fn v128_load8_splat_at(memory: &[u8], address: usize) -> Result = u8; fn v128_load16_splat_at(memory: &[u8], address: usize) -> Result = u16; fn v128_load32_splat_at(memory: &[u8], address: usize) -> Result = u32; fn v128_load64_splat_at(memory: &[u8], address: usize) -> Result = u64; } macro_rules! impl_v128_loadN_lane_for { ( $( fn $name:ident( memory: &[u8], ptr: u64, offset: u64, x: V128, lane: $lane_idx:ty $(,)? ) -> Result = $ty:ty; )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($name), "` instruction.")] /// /// # Errors /// /// - If `ptr + offset` overflows. /// - If `ptr + offset` loads out of bounds from `memory`. pub fn $name(memory: &[u8], ptr: u64, offset: u64, x: V128, lane: $lane_idx) -> Result { memory::load::<$ty>(memory, ptr, offset).map(|value| x.replace_lane(lane, value)) } )* }; } impl_v128_loadN_lane_for! { fn v128_load8_lane(memory: &[u8], ptr: u64, offset: u64, x: V128, lane: ImmLaneIdx16) -> Result = u8; fn v128_load16_lane(memory: &[u8], ptr: u64, offset: u64, x: V128, lane: ImmLaneIdx8) -> Result = u16; fn v128_load32_lane(memory: &[u8], ptr: u64, offset: u64, x: V128, lane: ImmLaneIdx4) -> Result = u32; fn v128_load64_lane(memory: &[u8], ptr: u64, offset: u64, x: V128, lane: ImmLaneIdx2) -> Result = u64; } macro_rules! impl_v128_loadN_lane_at_for { ( $( fn $name:ident(memory: &[u8], address: usize, x: V128, lane: $lane_idx:ty) -> Result = $ty:ty; )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($name), "` instruction.")] /// /// # Errors /// /// If `address` loads out of bounds from `memory`. pub fn $name(memory: &[u8], address: usize, x: V128, lane: $lane_idx) -> Result { memory::load_at::<$ty>(memory, address).map(|value| x.replace_lane(lane, value)) } )* }; } impl_v128_loadN_lane_at_for! { fn v128_load8_lane_at(memory: &[u8], address: usize, x: V128, lane: ImmLaneIdx16) -> Result = u8; fn v128_load16_lane_at(memory: &[u8], address: usize, x: V128, lane: ImmLaneIdx8) -> Result = u16; fn v128_load32_lane_at(memory: &[u8], address: usize, x: V128, lane: ImmLaneIdx4) -> Result = u32; fn v128_load64_lane_at(memory: &[u8], address: usize, x: V128, lane: ImmLaneIdx2) -> Result = u64; } /// Allows `Self` to be safely and efficiently split into `T`. /// /// Usually `T` is an array of `U` where `U` fits multiple times into `Self`. /// An example of this is that `u64` can be split into `[u32; 2]`. /// /// This is a helper trait to implement [`V128::load_nxm`] generically. trait SplitInto { type Output; fn split_into(self) -> Self::Output; } macro_rules! impl_split_into_for { ( $( impl SplitInto<$ty:ty> for u64; )* ) => { $( impl SplitInto<$ty> for u64 { type Output = [$ty; core::mem::size_of::() / core::mem::size_of::<$ty>()]; fn split_into(self) -> Self::Output { let bytes = self.to_ne_bytes(); array::from_fn(|i| { <$ty>::from_ne_bytes(array::from_fn(|j| { bytes[core::mem::size_of::<$ty>() * i + j] })) }) } } )* }; } impl_split_into_for! { impl SplitInto for u64; impl SplitInto for u64; impl SplitInto for u64; impl SplitInto for u64; impl SplitInto for u64; impl SplitInto for u64; } /// Allows to extend all items in an array from `T` to `Ext`. /// /// This is a helper trait to implement [`V128::load_nxm`] generically. trait ExtendArray { type Output; fn extend_array(self) -> Self::Output; } impl ExtendArray for [T; N] where T: ExtendInto, { type Output = [Ext; N]; fn extend_array(self) -> Self::Output { self.map(>::extend_into) } } impl V128 { /// Interprets `bits` as array of `Narrow` and distribute the (sign) extended items as [`V128`]. fn load_nxm(bits: u64) -> V128 where u64: SplitInto::Lanes>>>, Wide: IntoLanes, { bits.split_into().extend_array().into().into_v128() } } macro_rules! impl_v128_load_mxn { ( $( fn $name:ident(memory: &[u8], ptr: u64, offset: u64) -> Result = ($n:ty => $w:ty); )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($name), "` instruction.")] /// /// # Errors /// /// - If `ptr + offset` overflows. /// - If `ptr + offset` loads out of bounds from `memory`. pub fn $name(memory: &[u8], ptr: u64, offset: u64) -> Result { memory::load::(memory, ptr, offset).map(V128::load_nxm::<$n, $w>) } )* }; } impl_v128_load_mxn! { fn v128_load8x8_s(memory: &[u8], ptr: u64, offset: u64) -> Result = (i8 => i16); fn v128_load8x8_u(memory: &[u8], ptr: u64, offset: u64) -> Result = (u8 => u16); fn v128_load16x4_s(memory: &[u8], ptr: u64, offset: u64) -> Result = (i16 => i32); fn v128_load16x4_u(memory: &[u8], ptr: u64, offset: u64) -> Result = (u16 => u32); fn v128_load32x2_s(memory: &[u8], ptr: u64, offset: u64) -> Result = (i32 => i64); fn v128_load32x2_u(memory: &[u8], ptr: u64, offset: u64) -> Result = (u32 => u64); } macro_rules! impl_v128_load_mxn_at { ( $( fn $name:ident(memory: &[u8], address: usize) -> Result = ($n:ty => $w:ty); )* ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($name), "` instruction.")] /// /// # Errors /// /// If `address` loads out of bounds from `memory`. pub fn $name(memory: &[u8], address: usize) -> Result { memory::load_at::(memory, address).map(V128::load_nxm::<$n, $w>) } )* }; } impl_v128_load_mxn_at! { fn v128_load8x8_s_at(memory: &[u8], address: usize) -> Result = (i8 => i16); fn v128_load8x8_u_at(memory: &[u8], address: usize) -> Result = (u8 => u16); fn v128_load16x4_s_at(memory: &[u8], address: usize) -> Result = (i16 => i32); fn v128_load16x4_u_at(memory: &[u8], address: usize) -> Result = (u16 => u32); fn v128_load32x2_s_at(memory: &[u8], address: usize) -> Result = (i32 => i64); fn v128_load32x2_u_at(memory: &[u8], address: usize) -> Result = (u32 => u64); } macro_rules! impl_forwarding_relaxed_ops { ( $( fn $name:ident( $( $param_name:ident: $param_ty:ty ),* $(,)? ) -> $ret_ty:ty = $forward_fn:expr );* $(;)? ) => { $( #[doc = concat!("Executes a Wasm `", stringify!($name), "` instruction.")] #[doc = ""] #[doc = "# Note"] #[doc = ""] #[doc = "This is part of the `relaxed-simd` Wasm proposal."] pub fn $name( $( $param_name: $param_ty ),* ) -> $ret_ty { $forward_fn( $( $param_name ),* ) } )* }; } impl_forwarding_relaxed_ops! { fn i8x16_relaxed_swizzle(a: V128, s: V128) -> V128 = i8x16_swizzle; fn i8x16_relaxed_laneselect(a: V128, b: V128, c: V128) -> V128 = v128_bitselect; fn i16x8_relaxed_laneselect(a: V128, b: V128, c: V128) -> V128 = v128_bitselect; fn i32x4_relaxed_laneselect(a: V128, b: V128, c: V128) -> V128 = v128_bitselect; fn i64x2_relaxed_laneselect(a: V128, b: V128, c: V128) -> V128 = v128_bitselect; fn f32x4_relaxed_min(lhs: V128, rhs: V128) -> V128 = f32x4_min; fn f32x4_relaxed_max(lhs: V128, rhs: V128) -> V128 = f32x4_max; fn f64x2_relaxed_min(lhs: V128, rhs: V128) -> V128 = f64x2_min; fn f64x2_relaxed_max(lhs: V128, rhs: V128) -> V128 = f64x2_max; fn i16x8_relaxed_q15mulr_s(a: V128, b: V128) -> V128 = i16x8_q15mulr_sat_s; fn i32x4_relaxed_trunc_f32x4_s(input: V128) -> V128 = i32x4_trunc_sat_f32x4_s; fn i32x4_relaxed_trunc_f32x4_u(input: V128) -> V128 = i32x4_trunc_sat_f32x4_u; fn i32x4_relaxed_trunc_f64x2_s_zero(input: V128) -> V128 = i32x4_trunc_sat_f64x2_s_zero; fn i32x4_relaxed_trunc_f64x2_u_zero(input: V128) -> V128 = i32x4_trunc_sat_f64x2_u_zero; } #[test] fn i32x4_dot_i16x8_s_works() { assert_eq!( simd::i32x4_dot_i16x8_s(simd::i16x8_splat(16383_i16), simd::i16x8_splat(16384_i16)), simd::i32x4_splat(536838144_i32) ); } #[test] fn v128_or_works() { assert_eq!( simd::v128_or(simd::i16x8_splat(0), simd::i16x8_splat(0xffff_u16 as i16),), simd::i16x8_splat(0xffff_u16 as i16), ); } #[test] fn i8x16_narrow_i16x8_s_works() { assert_eq!( simd::i8x16_narrow_i16x8_s(simd::i16x8_splat(0x80_i16), simd::i16x8_splat(0x80_i16)), simd::i8x16_splat(0x7f), ); } wasmi_core-1.1.0/src/table/element.rs000064400000000000000000000047561046102023000156440ustar 00000000000000use crate::{UntypedVal, ValType}; use alloc::boxed::Box; /// A Wasm [`ElementSegment`]. #[derive(Debug)] pub struct ElementSegment { /// The [`ValType`] of elements of this [`ElementSegment`]. ty: ValType, /// Pre-resolved items of the Wasm element segment. items: Box<[UntypedVal]>, } impl ElementSegment { /// Creates a new [`ElementSegment`]. /// /// # Panics /// /// If the length of `items` exceeds `u32`. pub fn new(ty: ValType, items: I) -> Self where I: IntoIterator, { let items: Box<[UntypedVal]> = items.into_iter().collect(); assert!( u32::try_from(items.len()).is_ok(), "element segment has too many items: {}", items.len() ); Self { ty, items } } /// Returns `self` as [`ElementSegmentRef`]. pub fn as_ref(&self) -> ElementSegmentRef<'_> { ElementSegmentRef::from(self) } /// Returns the [`ValType`] of elements in the [`ElementSegment`]. pub fn ty(&self) -> ValType { self.ty } /// Returns the items of the [`ElementSegment`]. pub fn items(&self) -> &[UntypedVal] { &self.items[..] } /// Returns the number of items in the [`ElementSegment`]. pub fn size(&self) -> u32 { self.as_ref().size() } /// Drops the items of the [`ElementSegment`]. pub fn drop_items(&mut self) { self.items = [].into(); } } /// A shared reference to a Wasm [`ElementSegment`]. #[derive(Debug, Copy, Clone)] pub struct ElementSegmentRef<'a> { /// The [`ValType`] of elements of this [`ElementSegment`]. ty: ValType, /// The items of the Wasm element segment. items: &'a [UntypedVal], } impl<'a> From<&'a ElementSegment> for ElementSegmentRef<'a> { fn from(element: &'a ElementSegment) -> Self { Self { ty: element.ty(), items: element.items(), } } } impl<'a> ElementSegmentRef<'a> { /// Returns the [`ValType`] of elements in the [`ElementSegment`]. pub fn ty(self) -> ValType { self.ty } /// Returns the items of the [`ElementSegment`]. pub fn items(self) -> &'a [UntypedVal] { self.items } /// Returns the number of items in the [`ElementSegment`]. pub fn size(self) -> u32 { let len = self.items().len(); u32::try_from(len).unwrap_or_else(|_| { panic!("element segments are ensured to have at most 2^32 items but found: {len}") }) } } wasmi_core-1.1.0/src/table/error.rs000064400000000000000000000063761046102023000153440ustar 00000000000000use crate::{FuelError, LimiterError}; use core::{ error::Error, fmt::{self, Display}, }; /// Errors that may occur upon operating with table entities. #[derive(Debug, Copy, Clone)] #[non_exhaustive] pub enum TableError { /// Tried to allocate more virtual memory than technically possible. OutOfSystemMemory, /// The minimum size of the table type overflows the system index type. MinimumSizeOverflow, /// The maximum size of the table type overflows the system index type. MaximumSizeOverflow, /// If a resource limiter denied allocation or growth of a linear memory. ResourceLimiterDeniedAllocation, /// Occurs when growing a table out of its set bounds. GrowOutOfBounds, /// Occurs when initializing a table out of its set bounds. InitOutOfBounds, /// Occurs when filling a table out of its set bounds. FillOutOfBounds, /// Occurs when accessing the table out of bounds. SetOutOfBounds, /// Occur when coping elements of tables out of bounds. CopyOutOfBounds, /// Occurs when operating with a [`Table`](crate::Table) and mismatching element types. ElementTypeMismatch, /// The operation ran out of fuel before completion. OutOfFuel { required_fuel: u64 }, } impl Error for TableError {} impl Display for TableError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let message = match self { Self::OutOfSystemMemory => { "tried to allocate more virtual memory than available on the system" } Self::MinimumSizeOverflow => "the minimum table size overflows the system bounds", Self::MaximumSizeOverflow => "the maximum table size overflows the system bounds", Self::ResourceLimiterDeniedAllocation => { "a resource limiter denied to allocate or grow the table" } Self::GrowOutOfBounds => "out of bounds table access: `table.growth`", Self::InitOutOfBounds => "out of bounds table access: `table.init`", Self::FillOutOfBounds => "out of bounds table access: `table.fill`", Self::CopyOutOfBounds => "out of bounds table access: `table.copy`", Self::SetOutOfBounds => "out of bounds table access: `table.set`", Self::ElementTypeMismatch => "encountered mismatching table element type", Self::OutOfFuel { required_fuel } => { return write!(f, "not enough fuel: required={required_fuel}") } }; write!(f, "{message}") } } impl From for TableError { fn from(error: LimiterError) -> Self { match error { LimiterError::OutOfSystemMemory => Self::OutOfSystemMemory, LimiterError::OutOfBoundsGrowth => Self::GrowOutOfBounds, LimiterError::ResourceLimiterDeniedAllocation => Self::ResourceLimiterDeniedAllocation, LimiterError::OutOfFuel { required_fuel } => Self::OutOfFuel { required_fuel }, } } } impl From for TableError { fn from(error: FuelError) -> Self { match error { FuelError::OutOfFuel { required_fuel } => Self::OutOfFuel { required_fuel }, FuelError::FuelMeteringDisabled => panic!("fuel metering is disabled"), } } } wasmi_core-1.1.0/src/table/mod.rs000064400000000000000000000350631046102023000147650ustar 00000000000000mod element; mod error; mod ty; pub use self::{ element::{ElementSegment, ElementSegmentRef}, error::TableError, ty::TableType, }; use crate::{Fuel, FuelError, ResourceLimiterRef, TypedVal, UntypedVal}; use alloc::vec::Vec; use core::{cmp, iter}; #[cfg(test)] mod tests; /// A Wasm table entity. #[derive(Debug)] pub struct Table { ty: TableType, elements: Vec, } impl Table { /// Creates a new table entity with the given resizable limits. /// /// # Errors /// /// If `init` does not match the [`TableType`] element type. pub fn new( ty: TableType, init: TypedVal, limiter: &mut ResourceLimiterRef<'_>, ) -> Result { ty.ensure_element_type_matches(init.ty())?; let Ok(min_size) = usize::try_from(ty.minimum()) else { return Err(TableError::MinimumSizeOverflow); }; let Ok(max_size) = ty.maximum().map(usize::try_from).transpose() else { return Err(TableError::MaximumSizeOverflow); }; if let Some(limiter) = limiter.as_resource_limiter() { if !limiter.table_growing(0, min_size, max_size)? { return Err(TableError::ResourceLimiterDeniedAllocation); } } let mut elements = Vec::new(); if elements.try_reserve(min_size).is_err() { let error = TableError::OutOfSystemMemory; if let Some(limiter) = limiter.as_resource_limiter() { limiter.table_grow_failed(&error.into()) } return Err(error); }; elements.extend(iter::repeat_n::(init.into(), min_size)); Ok(Self { ty, elements }) } /// Returns the resizable limits of the table. pub fn ty(&self) -> TableType { self.ty } /// Returns the dynamic [`TableType`] of the [`Table`]. /// /// # Note /// /// This respects the current size of the [`Table`] /// as its minimum size and is useful for import subtyping checks. pub fn dynamic_ty(&self) -> TableType { TableType::new_impl( self.ty().element(), self.ty().index_ty(), self.size(), self.ty().maximum(), ) } /// Returns the current size of the [`Table`]. pub fn size(&self) -> u64 { let len = self.elements.len(); let Ok(len) = u64::try_from(len) else { panic!("`table.size` is out of system bounds: {len}"); }; len } /// Grows the table by the given amount of elements. /// /// Returns the old size of the [`Table`] upon success. /// /// # Note /// /// The newly added elements are initialized to the `init` [`TypedVal`]. /// /// # Errors /// /// - If the table is grown beyond its maximum limits. /// - If `value` does not match the [`Table`] element type. pub fn grow( &mut self, delta: u64, init: TypedVal, fuel: Option<&mut Fuel>, limiter: &mut ResourceLimiterRef<'_>, ) -> Result { self.ty().ensure_element_type_matches(init.ty())?; self.grow_untyped(delta, init.into(), fuel, limiter) } /// Grows the table by the given amount of elements. /// /// Returns the old size of the [`Table`] upon success. /// /// # Note /// /// This is an internal API that exists for efficiency purposes. /// /// The newly added elements are initialized to the `init` [`TypedVal`]. /// /// # Errors /// /// If the table is grown beyond its maximum limits. pub fn grow_untyped( &mut self, delta: u64, init: UntypedVal, fuel: Option<&mut Fuel>, limiter: &mut ResourceLimiterRef<'_>, ) -> Result { if delta == 0 { return Ok(self.size()); } let Ok(delta_size) = usize::try_from(delta) else { return Err(TableError::GrowOutOfBounds); }; let Some(desired) = self.size().checked_add(delta) else { return Err(TableError::GrowOutOfBounds); }; let max_size = self.ty.index_ty().max_size(); if u128::from(desired) >= max_size { return Err(TableError::GrowOutOfBounds); } let current = self.elements.len(); let Ok(desired) = usize::try_from(desired) else { return Err(TableError::GrowOutOfBounds); }; let Ok(maximum) = self.ty.maximum().map(usize::try_from).transpose() else { return Err(TableError::GrowOutOfBounds); }; // ResourceLimiter gets first look at the request. if let Some(limiter) = limiter.as_resource_limiter() { match limiter.table_growing(current, desired, maximum) { Ok(true) => (), Ok(false) => return Err(TableError::GrowOutOfBounds), Err(_) => return Err(TableError::ResourceLimiterDeniedAllocation), } } let notify_limiter = |limiter: &mut ResourceLimiterRef<'_>, error: TableError| -> Result { if let Some(limiter) = limiter.as_resource_limiter() { limiter.table_grow_failed(&error.into()); } Err(error) }; if let Some(maximum) = maximum { if desired > maximum { return notify_limiter(limiter, TableError::GrowOutOfBounds); } } if let Some(fuel) = fuel { match fuel.consume_fuel(|costs| costs.fuel_for_copying_values(delta)) { Ok(_) | Err(FuelError::FuelMeteringDisabled) => {} Err(FuelError::OutOfFuel { required_fuel }) => { return notify_limiter(limiter, TableError::OutOfFuel { required_fuel }) } } } if self.elements.try_reserve(delta_size).is_err() { return notify_limiter(limiter, TableError::OutOfSystemMemory); } let size_before = self.size(); self.elements.resize(desired, init); Ok(size_before) } /// Returns the [`Table`] element value at `index`. /// /// Returns `None` if `index` is out of bounds. pub fn get(&self, index: u64) -> Option { let untyped = self.get_untyped(index)?; let value = TypedVal::new(self.ty().element(), untyped); Some(value) } /// Returns the untyped [`Table`] element value at `index`. /// /// Returns `None` if `index` is out of bounds. /// /// # Note /// /// This is a more efficient version of [`Table::get`] for /// internal use only. pub fn get_untyped(&self, index: u64) -> Option { let index = usize::try_from(index).ok()?; self.elements.get(index).copied() } /// Sets the [`TypedVal`] of this [`Table`] at `index`. /// /// # Errors /// /// - If `index` is out of bounds. /// - If `value` does not match the [`Table`] element type. pub fn set(&mut self, index: u64, value: TypedVal) -> Result<(), TableError> { self.ty().ensure_element_type_matches(value.ty())?; self.set_untyped(index, value.into()) } /// Returns the [`UntypedVal`] of the [`Table`] at `index`. /// /// # Errors /// /// If `index` is out of bounds. pub fn set_untyped(&mut self, index: u64, value: UntypedVal) -> Result<(), TableError> { let Some(untyped) = self.elements.get_mut(index as usize) else { return Err(TableError::SetOutOfBounds); }; *untyped = value; Ok(()) } /// Initialize `len` elements from `src_element[src_index..]` into `self[dst_index..]`. /// /// # Errors /// /// Returns an error if the range is out of bounds of either the source or destination tables. /// /// # Panics /// /// If the [`ElementSegment`] element type does not match the [`Table`] element type. /// Note: This is a panic instead of an error since it is asserted at Wasm validation time. pub fn init( &mut self, element: ElementSegmentRef, dst_index: u64, src_index: u32, len: u32, fuel: Option<&mut Fuel>, ) -> Result<(), TableError> { let table_type = self.ty(); assert!( table_type.element().is_ref(), "table.init currently only works on reftypes" ); self.ty().ensure_element_type_matches(element.ty())?; // Convert parameters to indices. let Ok(dst_index) = usize::try_from(dst_index) else { return Err(TableError::InitOutOfBounds); }; let Ok(src_index) = usize::try_from(src_index) else { return Err(TableError::InitOutOfBounds); }; let Ok(len_size) = usize::try_from(len) else { return Err(TableError::InitOutOfBounds); }; // Perform bounds check before anything else. let dst_items = self .elements .get_mut(dst_index..) .and_then(|items| items.get_mut(..len_size)) .ok_or(TableError::InitOutOfBounds)?; let src_items = element .items() .get(src_index..) .and_then(|items| items.get(..len_size)) .ok_or(TableError::InitOutOfBounds)?; if len == 0 { // Bail out early if nothing needs to be initialized. // The Wasm spec demands to still perform the bounds check // so we cannot bail out earlier. return Ok(()); } if let Some(fuel) = fuel { fuel.consume_fuel_if(|costs| costs.fuel_for_copying_values(u64::from(len)))?; } // Perform the actual table initialization. dst_items.copy_from_slice(src_items); Ok(()) } /// Copy `len` elements from `src_table[src_index..]` into /// `dst_table[dst_index..]`. /// /// # Errors /// /// Returns an error if the range is out of bounds of either the source or /// destination tables. pub fn copy( dst_table: &mut Self, dst_index: u64, src_table: &Self, src_index: u64, len: u64, fuel: Option<&mut Fuel>, ) -> Result<(), TableError> { dst_table .ty() .ensure_element_type_matches(src_table.ty().element())?; // Turn parameters into proper slice indices. let Ok(src_index) = usize::try_from(src_index) else { return Err(TableError::CopyOutOfBounds); }; let Ok(dst_index) = usize::try_from(dst_index) else { return Err(TableError::CopyOutOfBounds); }; let Ok(len_size) = usize::try_from(len) else { return Err(TableError::CopyOutOfBounds); }; // Perform bounds check before anything else. let dst_items = dst_table .elements .get_mut(dst_index..) .and_then(|items| items.get_mut(..len_size)) .ok_or(TableError::CopyOutOfBounds)?; let src_items = src_table .elements .get(src_index..) .and_then(|items| items.get(..len_size)) .ok_or(TableError::CopyOutOfBounds)?; if let Some(fuel) = fuel { fuel.consume_fuel_if(|costs| costs.fuel_for_copying_values(len))?; } // Finally, copy elements in-place for the table. dst_items.copy_from_slice(src_items); Ok(()) } /// Copy `len` elements from `self[src_index..]` into `self[dst_index..]`. /// /// # Errors /// /// Returns an error if the range is out of bounds of the table. pub fn copy_within( &mut self, dst_index: u64, src_index: u64, len: u64, fuel: Option<&mut Fuel>, ) -> Result<(), TableError> { // These accesses just perform the bounds checks required by the Wasm spec. let max_offset = cmp::max(dst_index, src_index); max_offset .checked_add(len) .filter(|&offset| offset <= self.size()) .ok_or(TableError::CopyOutOfBounds)?; // Turn parameters into proper indices. let Ok(src_index) = usize::try_from(src_index) else { return Err(TableError::CopyOutOfBounds); }; let Ok(dst_index) = usize::try_from(dst_index) else { return Err(TableError::CopyOutOfBounds); }; let Ok(len_size) = usize::try_from(len) else { return Err(TableError::CopyOutOfBounds); }; if let Some(fuel) = fuel { fuel.consume_fuel_if(|costs| costs.fuel_for_copying_values(len))?; } // Finally, copy elements in-place for the table. self.elements .copy_within(src_index..src_index.wrapping_add(len_size), dst_index); Ok(()) } /// Fill `table[dst..(dst + len)]` with the given value. /// /// # Errors /// /// - If `val` has a type mismatch with the element type of the [`Table`]. /// - If the region to be filled is out of bounds for the [`Table`]. /// - If `val` originates from a different [`Store`] than the [`Table`]. /// /// # Panics /// /// If `ctx` does not own `dst_table` or `src_table`. /// /// [`Store`]: [`crate::Store`] pub fn fill( &mut self, dst: u64, val: TypedVal, len: u64, fuel: Option<&mut Fuel>, ) -> Result<(), TableError> { self.ty().ensure_element_type_matches(val.ty())?; self.fill_untyped(dst, val.into(), len, fuel) } /// Fill `table[dst..(dst + len)]` with the given value. /// /// # Note /// /// This is an API for internal use only and exists for efficiency reasons. /// /// # Errors /// /// - If the region to be filled is out of bounds for the [`Table`]. /// /// # Panics /// /// If `ctx` does not own `dst_table` or `src_table`. /// /// [`Store`]: [`crate::Store`] pub fn fill_untyped( &mut self, dst: u64, val: UntypedVal, len: u64, fuel: Option<&mut Fuel>, ) -> Result<(), TableError> { let Ok(dst_index) = usize::try_from(dst) else { return Err(TableError::FillOutOfBounds); }; let Ok(len_size) = usize::try_from(len) else { return Err(TableError::FillOutOfBounds); }; let dst = self .elements .get_mut(dst_index..) .and_then(|elements| elements.get_mut(..len_size)) .ok_or(TableError::FillOutOfBounds)?; if let Some(fuel) = fuel { fuel.consume_fuel_if(|costs| costs.fuel_for_copying_values(len))?; } dst.fill(val); Ok(()) } } wasmi_core-1.1.0/src/table/tests.rs000064400000000000000000000017161046102023000153460ustar 00000000000000use crate::{TableType, ValType}; fn table_type(element: ValType, minimum: u32, maximum: impl Into>) -> TableType { TableType::new(element, minimum, maximum.into()) } #[test] fn subtyping_works() { assert!(!table_type(ValType::I32, 0, 1).is_subtype_of(&table_type(ValType::F64, 0, 1))); assert!(table_type(ValType::I32, 0, 1).is_subtype_of(&table_type(ValType::I32, 0, 1))); assert!(table_type(ValType::I32, 0, 1).is_subtype_of(&table_type(ValType::I32, 0, 2))); assert!(!table_type(ValType::I32, 0, 2).is_subtype_of(&table_type(ValType::I32, 0, 1))); assert!(table_type(ValType::I32, 2, None).is_subtype_of(&table_type(ValType::I32, 1, None))); assert!(table_type(ValType::I32, 0, None).is_subtype_of(&table_type(ValType::I32, 0, None))); assert!(table_type(ValType::I32, 0, 1).is_subtype_of(&table_type(ValType::I32, 0, None))); assert!(!table_type(ValType::I32, 0, None).is_subtype_of(&table_type(ValType::I32, 0, 1))); } wasmi_core-1.1.0/src/table/ty.rs000064400000000000000000000073551046102023000146450ustar 00000000000000use crate::{IndexType, TableError, ValType}; #[cfg(doc)] use crate::Table; /// A Wasm table descriptor. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct TableType { /// The type of values stored in the [`Table`]. element: ValType, /// The minimum number of elements the [`Table`] must have. min: u64, /// The optional maximum number of elements the [`Table`] can have. /// /// If this is `None` then the [`Table`] is not limited in size. max: Option, /// The index type used by the [`Table`]. index_ty: IndexType, } impl TableType { /// Creates a new [`TableType`]. /// /// # Panics /// /// If `min` is greater than `max`. pub fn new(element: ValType, min: u32, max: Option) -> Self { Self::new_impl(element, IndexType::I32, u64::from(min), max.map(u64::from)) } /// Creates a new [`TableType`] with a 64-bit index type. /// /// # Note /// /// 64-bit tables are part of the [Wasm `memory64` proposal]. /// /// [Wasm `memory64` proposal]: https://github.com/WebAssembly/memory64 /// /// # Panics /// /// If `min` is greater than `max`. pub fn new64(element: ValType, min: u64, max: Option) -> Self { Self::new_impl(element, IndexType::I64, min, max) } /// Convenience constructor to create a new [`TableType`]. pub(crate) fn new_impl( element: ValType, index_ty: IndexType, min: u64, max: Option, ) -> Self { let absolute_max = index_ty.max_size(); assert!(u128::from(min) <= absolute_max); max.inspect(|&max| { assert!(min <= max && u128::from(max) <= absolute_max); }); Self { element, min, max, index_ty, } } /// Returns `true` if this is a 64-bit [`TableType`]. /// /// 64-bit memories are part of the Wasm `memory64` proposal. pub fn is_64(&self) -> bool { self.index_ty.is_64() } /// Returns the [`IndexType`] used by the [`TableType`]. pub fn index_ty(&self) -> IndexType { self.index_ty } /// Returns the [`ValType`] of elements stored in the table. pub fn element(&self) -> ValType { self.element } /// Returns minimum number of elements the table must have. pub fn minimum(&self) -> u64 { self.min } /// The optional maximum number of elements the table can have. /// /// If this returns `None` then the table is not limited in size. pub fn maximum(&self) -> Option { self.max } /// Returns `Ok` if the element type of `self` matches `ty`. /// /// # Errors /// /// Returns a [`TableError::ElementTypeMismatch`] otherwise. pub(crate) fn ensure_element_type_matches(&self, ty: ValType) -> Result<(), TableError> { if self.element() != ty { return Err(TableError::ElementTypeMismatch); } Ok(()) } /// Returns `true` if the [`TableType`] is a subtype of the `other` [`TableType`]. /// /// # Note /// /// This implements the [subtyping rules] according to the WebAssembly spec. /// /// [import subtyping]: /// https://webassembly.github.io/spec/core/valid/types.html#import-subtyping pub fn is_subtype_of(&self, other: &Self) -> bool { if self.is_64() != other.is_64() { return false; } if self.element() != other.element() { return false; } if self.minimum() < other.minimum() { return false; } match (self.maximum(), other.maximum()) { (_, None) => true, (Some(max), Some(other_max)) => max <= other_max, _ => false, } } } wasmi_core-1.1.0/src/trap.rs000064400000000000000000000264141046102023000140650ustar 00000000000000use crate::HostError; use alloc::{boxed::Box, string::String}; use core::fmt::{self, Display}; #[cfg(feature = "std")] use std::error::Error as StdError; /// Error type which can be returned by Wasm code or by the host environment. /// /// Under some conditions, Wasm execution may produce a [`Trap`], /// which immediately aborts execution. /// Traps cannot be handled by WebAssembly code, but are reported to the /// host embedder. #[derive(Debug)] pub struct Trap { /// The cloneable reason of a [`Trap`]. reason: Box, } #[test] fn trap_size() { assert_eq!( core::mem::size_of::(), core::mem::size_of::<*const ()>() ); } /// The reason of a [`Trap`]. #[derive(Debug)] enum TrapReason { /// Traps during Wasm execution. InstructionTrap(TrapCode), /// An `i32` exit status code. /// /// # Note /// /// This is useful for some WASI functions. I32Exit(i32), /// An error described by a display message. Message(Box), /// Traps and errors during host execution. Host(Box), } impl TrapReason { /// Returns the classic `i32` exit program code of a `Trap` if any. /// /// Otherwise returns `None`. pub fn i32_exit_status(&self) -> Option { if let Self::I32Exit(status) = self { return Some(*status); } None } /// Returns a shared reference to the [`HostError`] if any. #[inline] pub fn as_host(&self) -> Option<&dyn HostError> { if let Self::Host(host_error) = self { return Some(&**host_error); } None } /// Returns an exclusive reference to the [`HostError`] if any. #[inline] pub fn as_host_mut(&mut self) -> Option<&mut dyn HostError> { if let Self::Host(host_error) = self { return Some(&mut **host_error); } None } /// Consumes `self` to return the [`HostError`] if any. #[inline] pub fn into_host(self) -> Option> { if let Self::Host(host_error) = self { return Some(host_error); } None } /// Returns the [`TrapCode`] traps originating from Wasm execution. #[inline] pub fn trap_code(&self) -> Option { if let Self::InstructionTrap(trap_code) = self { return Some(*trap_code); } None } } impl Trap { /// Create a new [`Trap`] from the [`TrapReason`]. fn with_reason(reason: TrapReason) -> Self { Self { reason: Box::new(reason), } } /// Creates a new [`Trap`] described by a `message`. #[cold] // traps are exceptional, this helps move handling off the main path pub fn new(message: T) -> Self where T: Into, { Self::with_reason(TrapReason::Message(message.into().into_boxed_str())) } /// Downcasts the [`Trap`] into the `T: HostError` if possible. /// /// Returns `None` otherwise. #[inline] pub fn downcast_ref(&self) -> Option<&T> where T: HostError, { self.reason .as_host() .and_then(::downcast_ref) } /// Downcasts the [`Trap`] into the `T: HostError` if possible. /// /// Returns `None` otherwise. #[inline] pub fn downcast_mut(&mut self) -> Option<&mut T> where T: HostError, { self.reason .as_host_mut() .and_then(::downcast_mut) } /// Consumes `self` to downcast the [`Trap`] into the `T: HostError` if possible. /// /// Returns `None` otherwise. #[inline] pub fn downcast(self) -> Option where T: HostError, { self.reason .into_host() .and_then(|error| error.downcast().ok()) .map(|boxed| *boxed) } /// Creates a new `Trap` representing an explicit program exit with a classic `i32` /// exit status value. #[cold] // see Trap::new pub fn i32_exit(status: i32) -> Self { Self::with_reason(TrapReason::I32Exit(status)) } /// Returns the classic `i32` exit program code of a `Trap` if any. /// /// Otherwise returns `None`. #[inline] pub fn i32_exit_status(&self) -> Option { self.reason.i32_exit_status() } /// Returns the [`TrapCode`] traps originating from Wasm execution. #[inline] pub fn trap_code(&self) -> Option { self.reason.trap_code() } } impl From for Trap { #[cold] // see Trap::new fn from(error: TrapCode) -> Self { Self::with_reason(TrapReason::InstructionTrap(error)) } } impl From for Trap where E: HostError, { #[inline] #[cold] // see Trap::new fn from(host_error: E) -> Self { Self::with_reason(TrapReason::Host(Box::new(host_error))) } } impl Display for TrapReason { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::InstructionTrap(trap_code) => Display::fmt(trap_code, f), Self::I32Exit(status) => write!(f, "Exited with i32 exit status {status}"), Self::Message(message) => write!(f, "{message}"), Self::Host(host_error) => Display::fmt(host_error, f), } } } impl Display for Trap { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { ::fmt(&self.reason, f) } } #[cfg(feature = "std")] impl StdError for Trap { fn description(&self) -> &str { self.trap_code().map_or("", |code| code.trap_message()) } } /// An invalid [`TrapCode`] integer value. #[derive(Debug, Copy, Clone)] pub struct InvalidTrapCode; macro_rules! generate_trap_code { ( $( $(#[$attr:meta])* $ident:ident = $discr:literal ),* $(,)? ) => { /// Error type which can be thrown by wasm code or by host environment. /// /// See [`Trap`] for details. /// /// [`Trap`]: struct.Trap.html #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[repr(u8)] pub enum TrapCode { $( $( #[$attr] )* $ident = $discr ),* } impl From for u8 { fn from(trap_code: TrapCode) -> Self { trap_code as _ } } impl TryFrom for TrapCode { type Error = InvalidTrapCode; fn try_from(value: u8) -> Result { match value { $( $discr => Ok(TrapCode::$ident), )* _ => Err(InvalidTrapCode), } } } #[test] fn trap_code_conversion() { $( assert_eq!( TrapCode::try_from(TrapCode::$ident as u8).unwrap(), TrapCode::$ident, ); )* assert!(TrapCode::try_from(u8::MAX).is_err()); } }; } generate_trap_code! { /// Wasm code executed `unreachable` opcode. /// /// This indicates that unreachable Wasm code was actually reached. /// This opcode have a similar purpose as `ud2` in x86. UnreachableCodeReached = 0, /// Attempt to load or store at the address which /// lies outside of bounds of the memory. /// /// Since addresses are interpreted as unsigned integers, out of bounds access /// can't happen with negative addresses (i.e. they will always wrap). MemoryOutOfBounds = 1, /// Attempt to access table element at index which /// lies outside of bounds. /// /// This typically can happen when `call_indirect` is executed /// with index that lies out of bounds. /// /// Since indexes are interpreted as unsigned integers, out of bounds access /// can't happen with negative indexes (i.e. they will always wrap). TableOutOfBounds = 2, /// Indicates that a `call_indirect` instruction called a function at /// an uninitialized (i.e. `null`) table index. IndirectCallToNull = 3, /// Attempt to divide by zero. /// /// This trap typically can happen if `div` or `rem` is executed with /// zero as divider. IntegerDivisionByZero = 4, /// An integer arithmetic operation caused an overflow. /// /// This can happen when trying to do signed division (or get the remainder) /// -2N-1 over -1. This is because the result +2N-1 /// isn't representable as a N-bit signed integer. IntegerOverflow = 5, /// Attempted to make an invalid conversion to an integer type. /// /// This can for example happen when trying to truncate NaNs, /// infinity, or value for which the result is out of range into an integer. BadConversionToInteger = 6, /// Stack overflow. /// /// This is likely caused by some infinite or very deep recursion. /// Extensive inlining might also be the cause of stack overflow. StackOverflow = 7, /// Attempt to invoke a function with mismatching signature. /// /// This can happen with indirect calls as they always /// specify the expected signature of function. If an indirect call is executed /// with an index that points to a function with signature different of what is /// expected by this indirect call, this trap is raised. BadSignature = 8, /// This trap is raised when a WebAssembly execution ran out of fuel. /// /// The Wasmi execution engine can be configured to instrument its /// internal bytecode so that fuel is consumed for each executed instruction. /// This is useful to deterministically halt or yield a WebAssembly execution. OutOfFuel = 9, /// This trap is raised when a growth operation was attempted and an /// installed `wasmi::ResourceLimiter` returned `Err(...)` from the /// associated `table_growing` or `memory_growing` method, indicating a /// desire on the part of the embedder to trap the interpreter rather than /// merely fail the growth operation. GrowthOperationLimited = 10, } impl TrapCode { /// Returns the trap message as specified by the WebAssembly specification. /// /// # Note /// /// This API is primarily useful for the Wasm spec testsuite but might have /// other uses since it avoid heap memory allocation in certain cases. pub fn trap_message(&self) -> &'static str { match self { Self::UnreachableCodeReached => "wasm `unreachable` instruction executed", Self::MemoryOutOfBounds => "out of bounds memory access", Self::TableOutOfBounds => "undefined element: out of bounds table access", Self::IndirectCallToNull => "uninitialized element 2", // TODO: fixme, remove the trailing " 2" again Self::IntegerDivisionByZero => "integer divide by zero", Self::IntegerOverflow => "integer overflow", Self::BadConversionToInteger => "invalid conversion to integer", Self::StackOverflow => "call stack exhausted", Self::BadSignature => "indirect call type mismatch", Self::OutOfFuel => "all fuel consumed by WebAssembly", Self::GrowthOperationLimited => "growth operation limited", } } } impl Display for TrapCode { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.trap_message()) } } wasmi_core-1.1.0/src/typed.rs000064400000000000000000000074361046102023000142470ustar 00000000000000use crate::{UntypedVal, ValType, F32, F64, V128}; /// Types that are associated to a static Wasm type. pub trait Typed { /// The static associated Wasm type. const TY: ValType; } macro_rules! impl_typed_for { ( $( $ty:ty => $value_ty:expr );* $(;)? ) => { $( impl Typed for $ty { const TY: ValType = $value_ty; } )* }; } impl_typed_for! { bool => ValType::I32; i8 => ValType::I32; u8 => ValType::I32; i16 => ValType::I32; u16 => ValType::I32; i32 => ValType::I32; u32 => ValType::I32; i64 => ValType::I64; u64 => ValType::I64; f32 => ValType::F32; f64 => ValType::F64; F32 => ValType::F32; F64 => ValType::F64; V128 => ValType::V128; } impl From for UntypedVal { fn from(typed_value: TypedVal) -> Self { typed_value.value } } /// An [`UntypedVal`] with its assumed [`ValType`]. /// /// # Note /// /// We explicitly do not make use of the existing [`Val`] /// abstraction since [`Val`] is optimized towards being a /// user facing type whereas [`TypedVal`] is focusing on /// performance and efficiency in computations. /// /// [`Val`]: [`crate::core::Value`] #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct TypedVal { /// The type of the value. ty: ValType, /// The underlying raw value. value: UntypedVal, } impl TypedVal { /// Create a new [`TypedVal`]. pub fn new(ty: ValType, value: UntypedVal) -> Self { Self { ty, value } } /// Returns the [`ValType`] of the [`TypedVal`]. pub fn ty(&self) -> ValType { self.ty } /// Returns the [`UntypedVal`] of the [`TypedVal`]. pub fn untyped(&self) -> UntypedVal { self.value } } impl From for TypedVal where T: Typed + Into, { fn from(value: T) -> Self { Self::new(::TY, value.into()) } } macro_rules! impl_from_typed_value_for { ( $( $( #[$attr:meta] )* impl From for $ty:ty );* $(;)? ) => { $( $( #[$attr] )* impl From for $ty { fn from(typed_value: TypedVal) -> Self { // # Note // // We only use a `debug_assert` here instead of a proper `assert` // since the whole translation process assumes that Wasm validation // was already performed and thus type checking does not necessarily // need to happen redundantly outside of debug builds. debug_assert!( matches!(typed_value.ty, <$ty as Typed>::TY), "type mismatch: expected {:?} but found {:?}", <$ty as Typed>::TY, typed_value.ty, ); Self::from(typed_value.value) } } )* }; } impl_from_typed_value_for! { impl From for bool; impl From for i32; impl From for u32; impl From for i64; impl From for u64; impl From for f32; impl From for f64; #[cfg(feature = "simd")] impl From for V128; } macro_rules! impl_from_typed_value_as_for { ( $( $( #[$attr:meta] )* impl From for $ty:ty as $as:ty );* $(;)? ) => { $( $( #[$attr] )* impl From for $ty { fn from(typed_value: TypedVal) -> Self { <$as as From>::from(typed_value) as $ty } } )* }; } impl_from_typed_value_as_for! { impl From for i8 as i32; impl From for i16 as i32; impl From for u8 as u32; impl From for u16 as u32; } wasmi_core-1.1.0/src/untyped.rs000064400000000000000000000341561046102023000146110ustar 00000000000000#[cfg(feature = "simd")] use crate::V128; use crate::{F32, F64}; use core::{ error::Error, fmt::{self, Display}, }; /// An untyped value. /// /// Provides a dense and simple interface to all functional Wasm operations. #[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(not(feature = "simd"), repr(transparent))] #[cfg_attr(feature = "simd", repr(C))] pub struct UntypedVal { /// The low 64-bits of an [`UntypedVal`]. /// /// The low 64-bits are used to encode and decode all types that /// are convertible from and to an [`UntypedVal`] that fit into /// 64-bits such as `i32`, `i64`, `f32` and `f64`. pub(crate) lo64: u64, /// The high 64-bits of an [`UntypedVal`]. /// /// This is only used to encode or decode types which do not fit /// into the lower 64-bits part such as Wasm's `V128` or `i128`. #[cfg(feature = "simd")] pub(crate) hi64: u64, } /// Implemented by types that can be read (or decoded) as `T`. /// /// Mainly implemented by [`UntypedVal`]. pub trait ReadAs { /// Reads `self` as value of type `T`. fn read_as(&self) -> T; } impl ReadAs for UntypedVal { fn read_as(&self) -> UntypedVal { *self } } macro_rules! impl_read_as_for_int { ( $( $int:ty ),* $(,)? ) => { $( impl ReadAs<$int> for UntypedVal { fn read_as(&self) -> $int { self.read_lo64() as $int } } )* }; } impl_read_as_for_int!(i8, i16, i32, i64, u8, u16, u32, u64); macro_rules! impl_read_as_for_float { ( $( $float:ty ),* $(,)? ) => { $( impl ReadAs<$float> for UntypedVal { fn read_as(&self) -> $float { <$float>::from_bits(self.read_lo64() as _) } } )* }; } impl_read_as_for_float!(f32, f64); #[cfg(feature = "simd")] impl ReadAs for UntypedVal { fn read_as(&self) -> V128 { // Note: we can re-use the `From` impl since both types are of equal size. V128::from(*self) } } impl ReadAs for UntypedVal { fn read_as(&self) -> bool { self.read_lo64() != 0 } } /// Implemented by types that can be written to (or encoded) as `T`. /// /// Mainly implemented by [`UntypedVal`]. pub trait WriteAs { /// Writes to `self` as value of type `T`. fn write_as(&mut self, value: T); } impl WriteAs for UntypedVal { fn write_as(&mut self, value: UntypedVal) { *self = value; } } macro_rules! impl_write_as_for_int { ( $( $int:ty as $as:ty ),* $(,)? ) => { $( impl WriteAs<$int> for UntypedVal { #[allow(clippy::cast_lossless)] fn write_as(&mut self, value: $int) { self.write_lo64(value as $as as _) } } impl WriteAs<::core::num::NonZero<$int>> for UntypedVal { fn write_as(&mut self, value: ::core::num::NonZero<$int>) { >::write_as(self, value.get()) } } )* }; } impl_write_as_for_int!(i8 as u8, i16 as u16, i32 as u32, i64 as u64); macro_rules! impl_write_as_for_uint { ( $( $int:ty ),* $(,)? ) => { $( impl WriteAs<$int> for UntypedVal { #[allow(clippy::cast_lossless)] fn write_as(&mut self, value: $int) { self.write_lo64(value as _) } } impl WriteAs<::core::num::NonZero<$int>> for UntypedVal { fn write_as(&mut self, value: ::core::num::NonZero<$int>) { >::write_as(self, value.get()) } } )* }; } impl_write_as_for_uint!(u8, u16, u32, u64); impl WriteAs for UntypedVal { #[allow(clippy::cast_lossless)] fn write_as(&mut self, value: bool) { self.write_lo64(value as _) } } macro_rules! impl_write_as_for_float { ( $( $float:ty ),* $(,)? ) => { $( impl WriteAs<$float> for UntypedVal { #[allow(clippy::cast_lossless)] fn write_as(&mut self, value: $float) { self.write_lo64(<$float>::to_bits(value) as _) } } )* }; } impl_write_as_for_float!(f32, f64); #[cfg(feature = "simd")] impl WriteAs for UntypedVal { fn write_as(&mut self, value: V128) { // Note: we can re-use the `From` impl since both types are of equal size. *self = UntypedVal::from(value); } } impl UntypedVal { /// Reads the low 64-bit of the [`UntypedVal`]. /// /// In contract to [`UntypedVal::to_bits64`] this ignores the high-bits entirely. fn read_lo64(&self) -> u64 { self.lo64 } /// Writes the low 64-bit of the [`UntypedVal`]. fn write_lo64(&mut self, bits: u64) { self.lo64 = bits; } /// Creates an [`UntypedVal`] from the given lower 64-bit bits. /// /// This sets the high 64-bits to zero if any. pub const fn from_bits64(lo64: u64) -> Self { Self { lo64, #[cfg(feature = "simd")] hi64: 0, } } /// Returns the underlying lower 64-bits of the [`UntypedVal`]. /// /// This ignores the high 64-bits of the [`UntypedVal`] if any. pub const fn to_bits64(self) -> u64 { self.lo64 } } macro_rules! impl_from_untyped_for_int { ( $( $int:ty ),* $(,)? ) => { $( impl From for $int { fn from(untyped: UntypedVal) -> Self { untyped.to_bits64() as _ } } )* }; } impl_from_untyped_for_int!(i8, i16, i32, i64, u8, u16, u32, u64); macro_rules! impl_from_untyped_for_float { ( $( $float:ty ),* $(,)? ) => { $( impl From for $float { fn from(untyped: UntypedVal) -> Self { Self::from_bits(untyped.to_bits64() as _) } } )* }; } impl_from_untyped_for_float!(f32, f64, F32, F64); #[cfg(feature = "simd")] impl From for V128 { fn from(value: UntypedVal) -> Self { let u128 = (u128::from(value.hi64) << 64) | (u128::from(value.lo64)); Self::from(u128) } } #[cfg(feature = "simd")] impl From for UntypedVal { fn from(value: V128) -> Self { let u128 = value.as_u128(); let lo64 = u128 as u64; let hi64 = (u128 >> 64) as u64; Self { lo64, hi64 } } } impl From for bool { fn from(untyped: UntypedVal) -> Self { untyped.to_bits64() != 0 } } macro_rules! impl_from_unsigned_prim { ( $( $prim:ty ),* $(,)? ) => { $( impl From<$prim> for UntypedVal { #[allow(clippy::cast_lossless)] fn from(value: $prim) -> Self { Self::from_bits64(value as _) } } impl From<::core::num::NonZero<$prim>> for UntypedVal { fn from(value: ::core::num::NonZero<$prim>) -> Self { <_ as From<$prim>>::from(value.get()) } } )* }; } #[rustfmt::skip] impl_from_unsigned_prim!( u8, u16, u32, u64, ); impl From for UntypedVal { #[allow(clippy::cast_lossless)] fn from(value: bool) -> Self { Self::from_bits64(value as _) } } macro_rules! impl_from_signed_prim { ( $( $prim:ty as $base:ty ),* $(,)? ) => { $( impl From<$prim> for UntypedVal { #[allow(clippy::cast_lossless)] fn from(value: $prim) -> Self { Self::from_bits64(u64::from(value as $base)) } } impl From<::core::num::NonZero<$prim>> for UntypedVal { fn from(value: ::core::num::NonZero<$prim>) -> Self { <_ as From<$prim>>::from(value.get()) } } )* }; } #[rustfmt::skip] impl_from_signed_prim!( i8 as u8, i16 as u16, i32 as u32, i64 as u64, ); macro_rules! impl_from_float { ( $( $float:ty ),* $(,)? ) => { $( impl From<$float> for UntypedVal { fn from(value: $float) -> Self { Self::from_bits64(u64::from(value.to_bits())) } } )* }; } impl_from_float!(f32, f64, F32, F64); /// Macro to help implement generic trait implementations for tuple types. macro_rules! for_each_tuple { ($mac:ident) => { $mac!( 0 ); $mac!( 1 T1); $mac!( 2 T1 T2); $mac!( 3 T1 T2 T3); $mac!( 4 T1 T2 T3 T4); $mac!( 5 T1 T2 T3 T4 T5); $mac!( 6 T1 T2 T3 T4 T5 T6); $mac!( 7 T1 T2 T3 T4 T5 T6 T7); $mac!( 8 T1 T2 T3 T4 T5 T6 T7 T8); $mac!( 9 T1 T2 T3 T4 T5 T6 T7 T8 T9); $mac!(10 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10); $mac!(11 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11); $mac!(12 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12); $mac!(13 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13); $mac!(14 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14); $mac!(15 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15); $mac!(16 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15 T16); } } /// An error that may occur upon encoding or decoding slices of [`UntypedVal`]. #[derive(Debug, Copy, Clone)] pub enum UntypedError { /// The [`UntypedVal`] slice length did not match `Self`. InvalidLen, } impl UntypedError { /// Creates a new `InvalidLen` [`UntypedError`]. #[cold] pub fn invalid_len() -> Self { Self::InvalidLen } } impl Error for UntypedError {} impl Display for UntypedError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { UntypedError::InvalidLen => { write!(f, "mismatched length of the untyped slice",) } } } } impl UntypedVal { /// Decodes the slice of [`UntypedVal`] as a value of type `T`. /// /// # Note /// /// `T` can either be a single type or a tuple of types depending /// on the length of the `slice`. /// /// # Errors /// /// If the tuple length of `T` and the length of `slice` does not match. pub fn decode_slice(slice: &[Self]) -> Result where T: DecodeUntypedSlice, { ::decode_untyped_slice(slice) } /// Encodes the slice of [`UntypedVal`] from the given value of type `T`. /// /// # Note /// /// `T` can either be a single type or a tuple of types depending /// on the length of the `slice`. /// /// # Errors /// /// If the tuple length of `T` and the length of `slice` does not match. pub fn encode_slice(slice: &mut [Self], input: T) -> Result<(), UntypedError> where T: EncodeUntypedSlice, { ::encode_untyped_slice(input, slice) } } /// Tuple types that allow to decode a slice of [`UntypedVal`]. pub trait DecodeUntypedSlice: Sized { /// Decodes the slice of [`UntypedVal`] as a value of type `Self`. /// /// # Note /// /// `Self` can either be a single type or a tuple of types depending /// on the length of the `slice`. /// /// # Errors /// /// If the tuple length of `Self` and the length of `slice` does not match. fn decode_untyped_slice(params: &[UntypedVal]) -> Result; } impl DecodeUntypedSlice for T1 where T1: From, { #[inline] fn decode_untyped_slice(results: &[UntypedVal]) -> Result { <(T1,) as DecodeUntypedSlice>::decode_untyped_slice(results).map(|t| t.0) } } macro_rules! impl_decode_untyped_slice { ( $n:literal $( $tuple:ident )* ) => { impl<$($tuple),*> DecodeUntypedSlice for ($($tuple,)*) where $( $tuple: From ),* { #[allow(non_snake_case)] #[inline] fn decode_untyped_slice(results: &[UntypedVal]) -> Result { match results { &[ $($tuple),* ] => Ok(( $( <$tuple as From>::from($tuple), )* )), _ => Err(UntypedError::invalid_len()), } } } }; } for_each_tuple!(impl_decode_untyped_slice); /// Tuple types that allow to encode a slice of [`UntypedVal`]. pub trait EncodeUntypedSlice { /// Encodes the slice of [`UntypedVal`] from the given value of type `Self`. /// /// # Note /// /// `Self` can either be a single type or a tuple of types depending /// on the length of the `slice`. /// /// # Errors /// /// If the tuple length of `Self` and the length of `slice` does not match. fn encode_untyped_slice(self, results: &mut [UntypedVal]) -> Result<(), UntypedError>; } impl EncodeUntypedSlice for T1 where T1: Into, { #[inline] fn encode_untyped_slice(self, results: &mut [UntypedVal]) -> Result<(), UntypedError> { <(T1,) as EncodeUntypedSlice>::encode_untyped_slice((self,), results) } } macro_rules! impl_encode_untyped_slice { ( $n:literal $( $tuple:ident )* ) => { impl<$($tuple),*> EncodeUntypedSlice for ($($tuple,)*) where $( $tuple: Into ),* { #[allow(non_snake_case)] #[inline] fn encode_untyped_slice<'a>(self, results: &'a mut [UntypedVal]) -> Result<(), UntypedError> { let Ok(_results) = <&'a mut [UntypedVal; $n]>::try_from(results) else { return Err(UntypedError::invalid_len()) }; let ( $( $tuple ,)* ) = self; let mut _i = 0; $( _results[_i] = <$tuple as Into>::into($tuple); _i += 1; )* Ok(()) } } }; } for_each_tuple!(impl_encode_untyped_slice); wasmi_core-1.1.0/src/value.rs000064400000000000000000000474251046102023000142400ustar 00000000000000use crate::{hint::unlikely, TrapCode}; /// Type of a value. /// /// See [`Val`] for details. /// /// [`Val`]: enum.Value.html #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum ValType { /// 32-bit signed or unsigned integer. I32, /// 64-bit signed or unsigned integer. I64, /// 32-bit IEEE 754-2008 floating point number. F32, /// 64-bit IEEE 754-2008 floating point number. F64, /// A 128-bit Wasm `simd` proposal vector. V128, /// A nullable function reference. FuncRef, /// A nullable external reference. ExternRef, } impl ValType { /// Returns `true` if [`ValType`] is a Wasm numeric type. /// /// This is `true` for [`ValType::I32`], [`ValType::I64`], /// [`ValType::F32`] and [`ValType::F64`]. pub fn is_num(&self) -> bool { matches!(self, Self::I32 | Self::I64 | Self::F32 | Self::F64) } /// Returns `true` if [`ValType`] is a Wasm reference type. /// /// This is `true` for [`ValType::FuncRef`] and [`ValType::ExternRef`]. pub fn is_ref(&self) -> bool { matches!(self, Self::ExternRef | Self::FuncRef) } } /// Convert one type to another by rounding to the nearest integer towards zero. /// /// # Errors /// /// Traps when the input float cannot be represented by the target integer or /// when the input float is NaN. pub trait TryTruncateInto { /// Convert one type to another by rounding to the nearest integer towards zero. /// /// # Errors /// /// - If the input float value is NaN (not a number). /// - If the input float value cannot be represented using the truncated /// integer type. fn try_truncate_into(self) -> Result; } /// Convert one type to another by rounding to the nearest integer towards zero. /// /// # Note /// /// This has saturating semantics for when the integer cannot represent the float. /// /// Returns /// /// - `0` when the input is NaN. /// - `int::MIN` when the input is -INF. /// - `int::MAX` when the input is +INF. pub trait TruncateSaturateInto { /// Convert one type to another by rounding to the nearest integer towards zero. fn truncate_saturate_into(self) -> T; } /// Sign-extends `Self` integer type from `T` integer type. pub trait SignExtendFrom { /// Convert one type to another by extending with leading zeroes. fn sign_extend_from(self) -> Self; } /// Integer value. pub trait Integer: Sized + Unsigned { /// Returns `true` if `self` is zero. #[allow(clippy::wrong_self_convention)] fn is_zero(self) -> bool; /// Counts leading zeros in the bitwise representation of the value. fn leading_zeros(self) -> Self; /// Counts trailing zeros in the bitwise representation of the value. fn trailing_zeros(self) -> Self; /// Counts 1-bits in the bitwise representation of the value. fn count_ones(self) -> Self; /// Shift-left `self` by `other`. fn shl(lhs: Self, rhs: Self) -> Self; /// Signed shift-right `self` by `other`. fn shr_s(lhs: Self, rhs: Self) -> Self; /// Unsigned shift-right `self` by `other`. fn shr_u(lhs: Self, rhs: Self) -> Self; /// Get left bit rotation result. fn rotl(lhs: Self, rhs: Self) -> Self; /// Get right bit rotation result. fn rotr(lhs: Self, rhs: Self) -> Self; /// Signed integer division. /// /// # Errors /// /// If `other` is equal to zero. fn div_s(lhs: Self, rhs: Self) -> Result; /// Unsigned integer division. /// /// # Errors /// /// If `other` is equal to zero. fn div_u(lhs: Self::Uint, rhs: Self::Uint) -> Result; /// Signed integer remainder. /// /// # Errors /// /// If `other` is equal to zero. fn rem_s(lhs: Self, rhs: Self) -> Result; /// Unsigned integer remainder. /// /// # Errors /// /// If `other` is equal to zero. fn rem_u(lhs: Self::Uint, rhs: Self::Uint) -> Result; } /// Integer types that have an unsigned mirroring type. pub trait Unsigned { /// The unsigned type. type Uint; /// Converts `self` losslessly to the unsigned type. fn to_unsigned(self) -> Self::Uint; } impl Unsigned for i32 { type Uint = u32; #[inline] fn to_unsigned(self) -> Self::Uint { self as _ } } impl Unsigned for i64 { type Uint = u64; #[inline] fn to_unsigned(self) -> Self::Uint { self as _ } } /// Float-point value. pub trait Float: Sized { /// Get absolute value. fn abs(self) -> Self; /// Returns the largest integer less than or equal to a number. fn floor(self) -> Self; /// Returns the smallest integer greater than or equal to a number. fn ceil(self) -> Self; /// Returns the integer part of a number. fn trunc(self) -> Self; /// Returns the nearest integer to a number. Ties are round to even number. fn nearest(self) -> Self; /// Takes the square root of a number. fn sqrt(self) -> Self; /// Returns the minimum of the two numbers. fn min(lhs: Self, rhs: Self) -> Self; /// Returns the maximum of the two numbers. fn max(lhs: Self, rhs: Self) -> Self; /// Sets sign of this value to the sign of other value. fn copysign(lhs: Self, rhs: Self) -> Self; /// Fused multiply-add with a single rounding error. #[cfg(feature = "simd")] fn mul_add(a: Self, b: Self, c: Self) -> Self; } macro_rules! impl_try_truncate_into { (@primitive $from: ident, $into: ident, $rmin:literal, $rmax:literal) => { impl TryTruncateInto<$into, TrapCode> for $from { #[inline] fn try_truncate_into(self) -> Result<$into, TrapCode> { if self.is_nan() { return Err(TrapCode::BadConversionToInteger); } if self <= $rmin || self >= $rmax { return Err(TrapCode::IntegerOverflow); } Ok(self as _) } } impl TruncateSaturateInto<$into> for $from { #[inline] fn truncate_saturate_into(self) -> $into { if self.is_nan() { return <$into as Default>::default(); } if self.is_infinite() && self.is_sign_positive() { return <$into>::MAX; } if self.is_infinite() && self.is_sign_negative() { return <$into>::MIN; } self as _ } } }; } impl_try_truncate_into!(@primitive f32, i32, -2147483904.0_f32, 2147483648.0_f32); impl_try_truncate_into!(@primitive f32, u32, -1.0_f32, 4294967296.0_f32); impl_try_truncate_into!(@primitive f64, i32, -2147483649.0_f64, 2147483648.0_f64); impl_try_truncate_into!(@primitive f64, u32, -1.0_f64, 4294967296.0_f64); impl_try_truncate_into!(@primitive f32, i64, -9223373136366403584.0_f32, 9223372036854775808.0_f32); impl_try_truncate_into!(@primitive f32, u64, -1.0_f32, 18446744073709551616.0_f32); impl_try_truncate_into!(@primitive f64, i64, -9223372036854777856.0_f64, 9223372036854775808.0_f64); impl_try_truncate_into!(@primitive f64, u64, -1.0_f64, 18446744073709551616.0_f64); macro_rules! impl_sign_extend_from { ( $( impl SignExtendFrom<$from_type:ty> for $for_type:ty; )* ) => { $( impl SignExtendFrom<$from_type> for $for_type { #[inline] #[allow(clippy::cast_lossless)] fn sign_extend_from(self) -> Self { (self as $from_type) as Self } } )* }; } impl_sign_extend_from! { impl SignExtendFrom for i32; impl SignExtendFrom for i32; impl SignExtendFrom for i64; impl SignExtendFrom for i64; impl SignExtendFrom for i64; } macro_rules! impl_integer { ($ty:ty) => { impl Integer for $ty { #[inline] fn is_zero(self) -> bool { self == 0 } #[inline] #[allow(clippy::cast_lossless)] fn leading_zeros(self) -> Self { self.leading_zeros() as _ } #[inline] #[allow(clippy::cast_lossless)] fn trailing_zeros(self) -> Self { self.trailing_zeros() as _ } #[inline] #[allow(clippy::cast_lossless)] fn count_ones(self) -> Self { self.count_ones() as _ } #[inline] fn shl(lhs: Self, rhs: Self) -> Self { lhs.wrapping_shl(rhs as u32) } #[inline] fn shr_s(lhs: Self, rhs: Self) -> Self { lhs.wrapping_shr(rhs as u32) } #[inline] fn shr_u(lhs: Self, rhs: Self) -> Self { lhs.to_unsigned().wrapping_shr(rhs as u32) as _ } #[inline] fn rotl(lhs: Self, rhs: Self) -> Self { lhs.rotate_left(rhs as u32) } #[inline] fn rotr(lhs: Self, rhs: Self) -> Self { lhs.rotate_right(rhs as u32) } #[inline] fn div_s(lhs: Self, rhs: Self) -> Result { if unlikely(rhs == 0) { return Err(TrapCode::IntegerDivisionByZero); } let (result, overflow) = lhs.overflowing_div(rhs); if unlikely(overflow) { return Err(TrapCode::IntegerOverflow); } Ok(result) } #[inline] fn div_u(lhs: Self::Uint, rhs: Self::Uint) -> Result { if unlikely(rhs == 0) { return Err(TrapCode::IntegerDivisionByZero); } let (result, overflow) = lhs.overflowing_div(rhs); if unlikely(overflow) { return Err(TrapCode::IntegerOverflow); } Ok(result) } #[inline] fn rem_s(lhs: Self, rhs: Self) -> Result { if unlikely(rhs == 0) { return Err(TrapCode::IntegerDivisionByZero); } Ok(lhs.wrapping_rem(rhs)) } #[inline] fn rem_u(lhs: Self::Uint, rhs: Self::Uint) -> Result { if unlikely(rhs == 0) { return Err(TrapCode::IntegerDivisionByZero); } Ok(lhs.wrapping_rem(rhs)) } } }; } impl_integer!(i32); impl_integer!(i64); // We cannot call the math functions directly, because they are not all available in `core`. // In no-std cases we instead rely on `libm`. // These wrappers handle that delegation. macro_rules! impl_float { ($ty:ty) => { impl Float for $ty { #[inline] fn abs(self) -> Self { WasmFloatExt::abs(self) } #[inline] fn floor(self) -> Self { WasmFloatExt::floor(self) } #[inline] fn ceil(self) -> Self { WasmFloatExt::ceil(self) } #[inline] fn trunc(self) -> Self { WasmFloatExt::trunc(self) } #[inline] fn nearest(self) -> Self { WasmFloatExt::nearest(self) } #[inline] fn sqrt(self) -> Self { WasmFloatExt::sqrt(self) } #[inline] fn min(lhs: Self, rhs: Self) -> Self { // Note: equal to the unstable `f32::minimum` method. // // Once `f32::minimum` is stable we can simply use it here. if lhs < rhs { lhs } else if rhs < lhs { rhs } else if lhs == rhs { if lhs.is_sign_negative() && rhs.is_sign_positive() { lhs } else { rhs } } else { // At least one input is NaN. Use `+` to perform NaN propagation and quieting. lhs + rhs } } #[inline] fn max(lhs: Self, rhs: Self) -> Self { // Note: equal to the unstable `f32::maximum` method. // // Once `f32::maximum` is stable we can simply use it here. if lhs > rhs { lhs } else if rhs > lhs { rhs } else if lhs == rhs { if lhs.is_sign_positive() && rhs.is_sign_negative() { lhs } else { rhs } } else { // At least one input is NaN. Use `+` to perform NaN propagation and quieting. lhs + rhs } } #[inline] fn copysign(lhs: Self, rhs: Self) -> Self { WasmFloatExt::copysign(lhs, rhs) } #[inline] #[cfg(feature = "simd")] fn mul_add(a: Self, b: Self, c: Self) -> Self { WasmFloatExt::mul_add(a, b, c) } } }; } impl_float!(f32); impl_float!(f64); /// Low-level Wasm float interface to support `no_std` environments. /// /// # Dev. Note /// /// The problem is that in `no_std` builds the Rust standard library /// does not specify all of the below methods for `f32` and `f64`. /// Thus this trait serves as an adapter to import this functionality /// via `libm`. trait WasmFloatExt { /// Equivalent to the Wasm `{f32,f64}.abs` instructions. fn abs(self) -> Self; /// Equivalent to the Wasm `{f32,f64}.ceil` instructions. fn ceil(self) -> Self; /// Equivalent to the Wasm `{f32,f64}.floor` instructions. fn floor(self) -> Self; /// Equivalent to the Wasm `{f32,f64}.trunc` instructions. fn trunc(self) -> Self; /// Equivalent to the Wasm `{f32,f64}.sqrt` instructions. fn sqrt(self) -> Self; /// Equivalent to the Wasm `{f32,f64}.nearest` instructions. fn nearest(self) -> Self; /// Equivalent to the Wasm `{f32,f64}.copysign` instructions. fn copysign(self, other: Self) -> Self; /// Fused multiply-add with just 1 rounding error. #[cfg(feature = "simd")] fn mul_add(self, a: Self, b: Self) -> Self; } #[cfg(not(feature = "std"))] macro_rules! impl_wasm_float { ($ty:ty) => { impl WasmFloatExt for $ty { #[inline] fn abs(self) -> Self { >::fabs(self) } #[inline] fn ceil(self) -> Self { >::ceil(self) } #[inline] fn floor(self) -> Self { >::floor(self) } #[inline] fn trunc(self) -> Self { >::trunc(self) } #[inline] fn nearest(self) -> Self { let round = >::round(self); if ::abs(self - ::trunc(self)) != 0.5 { return round; } let rem = round % 2.0; if rem == 1.0 { ::floor(self) } else if rem == -1.0 { ::ceil(self) } else { round } } #[inline] fn sqrt(self) -> Self { >::sqrt(self) } #[inline] fn copysign(self, other: Self) -> Self { >::copysign(self, other) } #[inline] #[cfg(feature = "simd")] fn mul_add(self, a: Self, b: Self) -> Self { >::fma(self, a, b) } } }; } /// The Wasm `simd` proposal's `v128` type. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[repr(transparent)] pub struct V128([u8; 16]); impl From for V128 { fn from(value: u128) -> Self { Self(value.to_le_bytes()) } } impl V128 { /// Returns the `self` as a 128-bit Rust integer. pub fn as_u128(&self) -> u128 { u128::from_ne_bytes(self.0) } } /// Extension trait for `f32` and `f64` to turn a NaN value into a quiet-NaN value. #[cfg(feature = "std")] trait IntoQuietNan: Sized { /// Converts `self` into a quiet-NaN if `self` is a NaN, otherwise returns `None`. fn into_quiet_nan(self) -> Option; } #[cfg(feature = "std")] macro_rules! impl_into_quiet_nan { ( $( ($float:ty, $bits:ty, $mask:literal) );* $(;)? ) => { $( impl IntoQuietNan for $float { #[inline] fn into_quiet_nan(self) -> Option { const QUIET_BIT: $bits = $mask; if !self.is_nan() { return None; } Some(Self::from_bits(self.to_bits() | QUIET_BIT)) } } )* }; } #[cfg(feature = "std")] impl_into_quiet_nan! { (f32, u32, 0x0040_0000); (f64, u64, 0x0008_0000_0000_0000); } #[cfg(feature = "std")] macro_rules! impl_wasm_float { ($ty:ty) => { impl WasmFloatExt for $ty { #[inline] fn abs(self) -> Self { self.abs() } #[inline] fn ceil(self) -> Self { if let Some(qnan) = self.into_quiet_nan() { return qnan; } self.ceil() } #[inline] fn floor(self) -> Self { if let Some(qnan) = self.into_quiet_nan() { return qnan; } self.floor() } #[inline] fn trunc(self) -> Self { if let Some(qnan) = self.into_quiet_nan() { return qnan; } self.trunc() } #[inline] fn nearest(self) -> Self { if let Some(qnan) = self.into_quiet_nan() { return qnan; } self.round_ties_even() } #[inline] fn sqrt(self) -> Self { if let Some(qnan) = self.into_quiet_nan() { return qnan; } self.sqrt() } #[inline] fn copysign(self, other: Self) -> Self { self.copysign(other) } #[inline] #[cfg(feature = "simd")] fn mul_add(self, a: Self, b: Self) -> Self { self.mul_add(a, b) } } }; } impl_wasm_float!(f32); impl_wasm_float!(f64); #[cfg(test)] mod tests { use super::*; #[test] fn wasm_float_min_regression_works() { assert_eq!(Float::min(-0.0_f32, 0.0_f32).to_bits(), 0x8000_0000); assert_eq!(Float::min(0.0_f32, -0.0_f32).to_bits(), 0x8000_0000); } #[test] fn wasm_float_max_regression_works() { assert_eq!(Float::max(-0.0_f32, 0.0_f32).to_bits(), 0x0000_0000); assert_eq!(Float::max(0.0_f32, -0.0_f32).to_bits(), 0x0000_0000); } #[test] fn copysign_regression_works() { // This test has been directly extracted from a WebAssembly Specification assertion. assert!(f32::from_bits(0xFFC00000).is_nan()); assert_eq!( Float::copysign(f32::from_bits(0xFFC00000), f32::from_bits(0x0000_0000)).to_bits(), 0x7FC00000, ) } } wasmi_core-1.1.0/src/wasm.rs000064400000000000000000000507731046102023000140730ustar 00000000000000//! Execution helpers for Wasm or Wasmi instructions. use crate::{ memory, Float, Integer, SignExtendFrom, TrapCode, TruncateSaturateInto, TryTruncateInto, }; use core::ops::Neg; macro_rules! op { ( $operator:tt ) => {{ |lhs, rhs| lhs $operator rhs }}; } macro_rules! impl_untyped_val { ( $(#[$attr:meta])* fn $name:ident(value: $ty:ty) -> Result<$ret_ty:ty> = $f:expr; $($tt:tt)* ) => { #[doc = concat!("Execute the `", stringify!($name), "` Wasm instruction.")] /// /// # Errors /// $( #[$attr] )* #[inline] pub fn $name(value: $ty) -> Result<$ret_ty, TrapCode> { ($f)(value) } impl_untyped_val!( $($tt)* ); }; ( fn $name:ident(value: $ty:ty) -> $ret_ty:ty = $f:expr; $($tt:tt)* ) => { #[doc = concat!("Execute the `", stringify!($name), "` Wasm instruction.")] #[inline] pub fn $name(value: $ty) -> $ret_ty { ($f)(value) } impl_untyped_val!( $($tt)* ); }; ( $(#[$attr:meta])* fn $name:ident(lhs: $lhs_ty:ty, rhs: $rhs_ty:ty) -> Result<$ret_ty:ty> = $f:expr; $($tt:tt)* ) => { #[doc = concat!("Execute the fallible `", stringify!($name), "` Wasm instruction.")] /// /// # Errors /// $( #[$attr] )* #[inline] pub fn $name(lhs: $lhs_ty, rhs: $rhs_ty) -> Result<$ret_ty, TrapCode> { ($f)(lhs, rhs) } impl_untyped_val!( $($tt)* ); }; ( fn $name:ident(lhs: $lhs_ty:ty, rhs: $rhs_ty:ty) -> $ret_ty:ty = $f:expr; $($tt:tt)* ) => { #[doc = concat!("Execute the `", stringify!($name), "` Wasm instruction.")] #[inline] pub fn $name(lhs: $lhs_ty, rhs: $rhs_ty) -> $ret_ty { ($f)(lhs, rhs) } impl_untyped_val!( $($tt)* ); }; () => {}; } impl_untyped_val! { // Wasm Integer Instructions fn i32_add(lhs: i32, rhs: i32) -> i32 = i32::wrapping_add; fn i64_add(lhs: i64, rhs: i64) -> i64 = i64::wrapping_add; fn i32_sub(lhs: i32, rhs: i32) -> i32 = i32::wrapping_sub; fn i64_sub(lhs: i64, rhs: i64) -> i64 = i64::wrapping_sub; fn i32_mul(lhs: i32, rhs: i32) -> i32 = i32::wrapping_mul; fn i64_mul(lhs: i64, rhs: i64) -> i64 = i64::wrapping_mul; fn i32_bitand(lhs: i32, rhs: i32) -> i32 = op!(&); fn i64_bitand(lhs: i64, rhs: i64) -> i64 = op!(&); fn i32_bitor(lhs: i32, rhs: i32) -> i32 = op!(|); fn i64_bitor(lhs: i64, rhs: i64) -> i64 = op!(|); fn i32_bitxor(lhs: i32, rhs: i32) -> i32 = op!(^); fn i64_bitxor(lhs: i64, rhs: i64) -> i64 = op!(^); fn i32_shl(lhs: i32, rhs: i32) -> i32 = Integer::shl; fn i64_shl(lhs: i64, rhs: i64) -> i64 = Integer::shl; fn i32_shr_s(lhs: i32, rhs: i32) -> i32 = Integer::shr_s; fn i64_shr_s(lhs: i64, rhs: i64) -> i64 = Integer::shr_s; fn i32_shr_u(lhs: i32, rhs: i32) -> i32 = Integer::shr_u; fn i64_shr_u(lhs: i64, rhs: i64) -> i64 = Integer::shr_u; fn i32_rotl(lhs: i32, rhs: i32) -> i32 = Integer::rotl; fn i64_rotl(lhs: i64, rhs: i64) -> i64 = Integer::rotl; fn i32_rotr(lhs: i32, rhs: i32) -> i32 = Integer::rotr; fn i64_rotr(lhs: i64, rhs: i64) -> i64 = Integer::rotr; } impl_untyped_val! { // Wasm Integer Division and Remainder Instructions /// - [`TrapCode::IntegerDivisionByZero`]: if `rhs` is zero. /// - [`TrapCode::IntegerOverflow`]: if `lhs` is [`i32::MIN`] and `rhs` is `-1`. fn i32_div_s(lhs: i32, rhs: i32) -> Result = Integer::div_s; /// - [`TrapCode::IntegerDivisionByZero`]: if `rhs` is zero. /// - [`TrapCode::IntegerOverflow`]: if `lhs` is [`i32::MIN`] and `rhs` is `-1`. fn i64_div_s(lhs: i64, rhs: i64) -> Result = Integer::div_s; /// - [`TrapCode::IntegerDivisionByZero`]: if `rhs` is zero. /// - [`TrapCode::IntegerOverflow`]: if `lhs` is [`i32::MIN`] and `rhs` is `-1`. fn i32_div_u(lhs: u32, rhs: u32) -> Result = ::div_u; /// - [`TrapCode::IntegerDivisionByZero`]: if `rhs` is zero. /// - [`TrapCode::IntegerOverflow`]: if `lhs` is [`i32::MIN`] and `rhs` is `-1`. fn i64_div_u(lhs: u64, rhs: u64) -> Result = ::div_u; /// - [`TrapCode::IntegerDivisionByZero`]: if `rhs` is zero. /// - [`TrapCode::IntegerOverflow`]: if `lhs` is [`i32::MIN`] and `rhs` is `-1`. fn i32_rem_s(lhs: i32, rhs: i32) -> Result = Integer::rem_s; /// - [`TrapCode::IntegerDivisionByZero`]: if `rhs` is zero. /// - [`TrapCode::IntegerOverflow`]: if `lhs` is [`i32::MIN`] and `rhs` is `-1`. fn i64_rem_s(lhs: i64, rhs: i64) -> Result = Integer::rem_s; /// - [`TrapCode::IntegerDivisionByZero`]: if `rhs` is zero. /// - [`TrapCode::IntegerOverflow`]: if `lhs` is [`i32::MIN`] and `rhs` is `-1`. fn i32_rem_u(lhs: u32, rhs: u32) -> Result = ::rem_u; /// - [`TrapCode::IntegerDivisionByZero`]: if `rhs` is zero. /// - [`TrapCode::IntegerOverflow`]: if `lhs` is [`i32::MIN`] and `rhs` is `-1`. fn i64_rem_u(lhs: u64, rhs: u64) -> Result = ::rem_u; } impl_untyped_val! { // Wasm Unary Instructions fn i32_clz(value: i32) -> i32 = Integer::leading_zeros; fn i64_clz(value: i64) -> i64 = Integer::leading_zeros; fn i32_ctz(value: i32) -> i32 = Integer::trailing_zeros; fn i64_ctz(value: i64) -> i64 = Integer::trailing_zeros; fn i32_popcnt(value: i32) -> i32 = Integer::count_ones; fn i64_popcnt(value: i64) -> i64 = Integer::count_ones; fn i32_eqz(value: i32) -> bool = Integer::is_zero; fn i64_eqz(value: i64) -> bool = Integer::is_zero; } impl_untyped_val! { // Wasm Comparison Instructions fn i32_eq(lhs: i32, rhs: i32) -> bool = op!(==); fn i64_eq(lhs: i64, rhs: i64) -> bool = op!(==); fn f32_eq(lhs: f32, rhs: f32) -> bool = op!(==); fn f64_eq(lhs: f64, rhs: f64) -> bool = op!(==); fn i32_ne(lhs: i32, rhs: i32) -> bool = op!(!=); fn i64_ne(lhs: i64, rhs: i64) -> bool = op!(!=); fn f32_ne(lhs: f32, rhs: f32) -> bool = op!(!=); fn f64_ne(lhs: f64, rhs: f64) -> bool = op!(!=); fn i32_lt_s(lhs: i32, rhs: i32) -> bool = op!(<); fn i64_lt_s(lhs: i64, rhs: i64) -> bool = op!(<); fn i32_lt_u(lhs: u32, rhs: u32) -> bool = op!(<); fn i64_lt_u(lhs: u64, rhs: u64) -> bool = op!(<); fn f32_lt(lhs: f32, rhs: f32) -> bool = op!(<); fn f64_lt(lhs: f64, rhs: f64) -> bool = op!(<); fn i32_le_s(lhs: i32, rhs: i32) -> bool = op!(<=); fn i64_le_s(lhs: i64, rhs: i64) -> bool = op!(<=); fn i32_le_u(lhs: u32, rhs: u32) -> bool = op!(<=); fn i64_le_u(lhs: u64, rhs: u64) -> bool = op!(<=); fn f32_le(lhs: f32, rhs: f32) -> bool = op!(<=); fn f64_le(lhs: f64, rhs: f64) -> bool = op!(<=); fn i32_gt_s(lhs: i32, rhs: i32) -> bool = op!(>); fn i64_gt_s(lhs: i64, rhs: i64) -> bool = op!(>); fn i32_gt_u(lhs: u32, rhs: u32) -> bool = op!(>); fn i64_gt_u(lhs: u64, rhs: u64) -> bool = op!(>); fn f32_gt(lhs: f32, rhs: f32) -> bool = op!(>); fn f64_gt(lhs: f64, rhs: f64) -> bool = op!(>); fn i32_ge_s(lhs: i32, rhs: i32) -> bool = op!(>=); fn i64_ge_s(lhs: i64, rhs: i64) -> bool = op!(>=); fn i32_ge_u(lhs: u32, rhs: u32) -> bool = op!(>=); fn i64_ge_u(lhs: u64, rhs: u64) -> bool = op!(>=); fn f32_ge(lhs: f32, rhs: f32) -> bool = op!(>=); fn f64_ge(lhs: f64, rhs: f64) -> bool = op!(>=); } impl_untyped_val! { // Wasm Float Instructions fn f32_abs(value: f32) -> f32 = Float::abs; fn f64_abs(value: f64) -> f64 = Float::abs; fn f32_neg(value: f32) -> f32 = Neg::neg; fn f64_neg(value: f64) -> f64 = Neg::neg; fn f32_ceil(value: f32) -> f32 = Float::ceil; fn f64_ceil(value: f64) -> f64 = Float::ceil; fn f32_floor(value: f32) -> f32 = Float::floor; fn f64_floor(value: f64) -> f64 = Float::floor; fn f32_trunc(value: f32) -> f32 = Float::trunc; fn f64_trunc(value: f64) -> f64 = Float::trunc; fn f32_nearest(value: f32) -> f32 = Float::nearest; fn f64_nearest(value: f64) -> f64 = Float::nearest; fn f32_sqrt(value: f32) -> f32 = Float::sqrt; fn f64_sqrt(value: f64) -> f64 = Float::sqrt; fn f32_add(lhs: f32, rhs: f32) -> f32 = op!(+); fn f64_add(lhs: f64, rhs: f64) -> f64 = op!(+); fn f32_sub(lhs: f32, rhs: f32) -> f32 = op!(-); fn f64_sub(lhs: f64, rhs: f64) -> f64 = op!(-); fn f32_mul(lhs: f32, rhs: f32) -> f32 = op!(*); fn f64_mul(lhs: f64, rhs: f64) -> f64 = op!(*); fn f32_div(lhs: f32, rhs: f32) -> f32 = op!(/); fn f64_div(lhs: f64, rhs: f64) -> f64 = op!(/); fn f32_min(lhs: f32, rhs: f32) -> f32 = Float::min; fn f64_min(lhs: f64, rhs: f64) -> f64 = Float::min; fn f32_max(lhs: f32, rhs: f32) -> f32 = Float::max; fn f64_max(lhs: f64, rhs: f64) -> f64 = Float::max; fn f32_copysign(lhs: f32, rhs: f32) -> f32 = Float::copysign; fn f64_copysign(lhs: f64, rhs: f64) -> f64 = Float::copysign; } impl_untyped_val! { // Wasm Conversion Routines fn i32_wrap_i64(value: i64) -> i32 = |v| v as i32; fn i64_extend_i32_s(value: i32) -> i64 = i64::from; fn i64_extend_i32_u(value: u32) -> u64 = u64::from; fn f32_demote_f64(value: f64) -> f32 = |v| v as f32; fn f64_promote_f32(value: f32) -> f64 = f64::from; /// - [`TrapCode::BadConversionToInteger`]: if `value` is NaN /// - [`TrapCode::IntegerOverflow`]: if `value` exceeds the bounds of an `i32` value fn i32_trunc_f32_s(value: f32) -> Result = TryTruncateInto::try_truncate_into; /// - [`TrapCode::BadConversionToInteger`]: if `value` is NaN /// - [`TrapCode::IntegerOverflow`]: if `value` exceeds the bounds of an `i64` value fn i64_trunc_f32_s(value: f32) -> Result = TryTruncateInto::try_truncate_into; /// - [`TrapCode::BadConversionToInteger`]: if `value` is NaN /// - [`TrapCode::IntegerOverflow`]: if `value` exceeds the bounds of an `u32` value fn i32_trunc_f32_u(value: f32) -> Result = TryTruncateInto::try_truncate_into; /// - [`TrapCode::BadConversionToInteger`]: if `value` is NaN /// - [`TrapCode::IntegerOverflow`]: if `value` exceeds the bounds of an `u64` value fn i64_trunc_f32_u(value: f32) -> Result = TryTruncateInto::try_truncate_into; /// - [`TrapCode::BadConversionToInteger`]: if `value` is NaN /// - [`TrapCode::IntegerOverflow`]: if `value` exceeds the bounds of an `i32` value fn i32_trunc_f64_s(value: f64) -> Result = TryTruncateInto::try_truncate_into; /// - [`TrapCode::BadConversionToInteger`]: if `value` is NaN /// - [`TrapCode::IntegerOverflow`]: if `value` exceeds the bounds of an `i64` value fn i64_trunc_f64_s(value: f64) -> Result = TryTruncateInto::try_truncate_into; /// - [`TrapCode::BadConversionToInteger`]: if `value` is NaN /// - [`TrapCode::IntegerOverflow`]: if `value` exceeds the bounds of an `u32` value fn i32_trunc_f64_u(value: f64) -> Result = TryTruncateInto::try_truncate_into; /// - [`TrapCode::BadConversionToInteger`]: if `value` is NaN /// - [`TrapCode::IntegerOverflow`]: if `value` exceeds the bounds of an `u64` value fn i64_trunc_f64_u(value: f64) -> Result = TryTruncateInto::try_truncate_into; fn f32_convert_i32_s(value: i32) -> f32 = |v| v as f32; fn f32_convert_i32_u(value: u32) -> f32 = |v| v as f32; fn f32_convert_i64_s(value: i64) -> f32 = |v| v as f32; fn f32_convert_i64_u(value: u64) -> f32 = |v| v as f32; fn f64_convert_i32_s(value: i32) -> f64 = f64::from; fn f64_convert_i32_u(value: u32) -> f64 = f64::from; fn f64_convert_i64_s(value: i64) -> f64 = |v| v as f64; fn f64_convert_i64_u(value: u64) -> f64 = |v| v as f64; } impl_untyped_val! { // Wasm `sign-extension` proposal fn i32_extend8_s(value: i32) -> i32 = <_ as SignExtendFrom>::sign_extend_from; fn i32_extend16_s(value: i32) -> i32 = <_ as SignExtendFrom>::sign_extend_from; fn i64_extend8_s(value: i64) -> i64 = <_ as SignExtendFrom>::sign_extend_from; fn i64_extend16_s(value: i64) -> i64 = <_ as SignExtendFrom>::sign_extend_from; fn i64_extend32_s(value: i64) -> i64 = <_ as SignExtendFrom>::sign_extend_from; } impl_untyped_val! { // Wasm `saturating-float-to-int` proposal fn i32_trunc_sat_f32_s(value: f32) -> i32 = TruncateSaturateInto::truncate_saturate_into; fn i32_trunc_sat_f32_u(value: f32) -> u32 = TruncateSaturateInto::truncate_saturate_into; fn i32_trunc_sat_f64_s(value: f64) -> i32 = TruncateSaturateInto::truncate_saturate_into; fn i32_trunc_sat_f64_u(value: f64) -> u32 = TruncateSaturateInto::truncate_saturate_into; fn i64_trunc_sat_f32_s(value: f32) -> i64 = TruncateSaturateInto::truncate_saturate_into; fn i64_trunc_sat_f32_u(value: f32) -> u64 = TruncateSaturateInto::truncate_saturate_into; fn i64_trunc_sat_f64_s(value: f64) -> i64 = TruncateSaturateInto::truncate_saturate_into; fn i64_trunc_sat_f64_u(value: f64) -> u64 = TruncateSaturateInto::truncate_saturate_into; } macro_rules! impl_reinterpret_cast { ( $(fn $name:ident($from:ty) -> $to:ty);* $(;)? ) => { $( #[doc = concat!("Execute the `", stringify!($name), "` Wasm instruction.")] pub fn $name(value: $from) -> $to { <$to>::from_ne_bytes(<$from>::to_ne_bytes(value)) } )* }; } impl_reinterpret_cast! { fn i32_reinterpret_f32(f32) -> i32; fn i64_reinterpret_f64(f64) -> i64; fn f32_reinterpret_i32(i32) -> f32; fn f64_reinterpret_i64(i64) -> f64; } macro_rules! gen_load_extend_fn { ( $( (fn $load_fn:ident, fn $load_at_fn:ident, $wrapped:ty => $ty:ty); )* ) => { $( #[doc = concat!("Executes a Wasmi `", stringify!($load_fn), "` instruction.")] /// /// # Errors /// /// - If `ptr + offset` overflows. /// - If `ptr + offset` loads out of bounds from `memory`. #[inline] pub fn $load_fn(memory: &[u8], ptr: u64, offset: u64) -> Result<$ty, TrapCode> { memory::load_extend::<$ty, $wrapped>(memory, ptr, offset) } #[doc = concat!("Executes a Wasmi `", stringify!($load_at_fn), "` instruction.")] /// /// # Errors /// /// If `address` loads out of bounds from `memory`. #[inline] pub fn $load_at_fn(memory: &[u8], address: usize) -> Result<$ty, TrapCode> { memory::load_extend_at::<$ty, $wrapped>(memory, address) } )* }; } gen_load_extend_fn! { (fn i32_load8_s, fn i32_load8_s_at, i8 => i32); (fn i32_load8_u, fn i32_load8_u_at, u8 => i32); (fn i32_load16_s, fn i32_load16_s_at, i16 => i32); (fn i32_load16_u, fn i32_load16_u_at, u16 => i32); (fn i64_load8_s, fn i64_load8_s_at, i8 => i64); (fn i64_load8_u, fn i64_load8_u_at, u8 => i64); (fn i64_load16_s, fn i64_load16_s_at, i16 => i64); (fn i64_load16_u, fn i64_load16_u_at, u16 => i64); (fn i64_load32_s, fn i64_load32_s_at, i32 => i64); (fn i64_load32_u, fn i64_load32_u_at, u32 => i64); } macro_rules! gen_load_fn { ( $( (fn $load_fn:ident, fn $load_at_fn:ident, $ty:ty); )* ) => { $( #[doc = concat!("Executes a Wasmi `", stringify!($load_fn), "` instruction.")] /// /// # Errors /// /// - If `ptr + offset` overflows. /// - If `ptr + offset` loads out of bounds from `memory`. #[inline] pub fn $load_fn(memory: &[u8], ptr: u64, offset: u64) -> Result<$ty, TrapCode> { memory::load::<$ty>(memory, ptr, offset) } #[doc = concat!("Executes a Wasmi `", stringify!($load_at_fn), "` instruction.")] /// /// # Errors /// /// If `address` loads out of bounds from `memory`. #[inline] pub fn $load_at_fn(memory: &[u8], address: usize) -> Result<$ty, TrapCode> { memory::load_at::<$ty>(memory, address) } )* }; } gen_load_fn! { (fn load32, fn load32_at, u32); (fn load64, fn load64_at, u64); } macro_rules! gen_store_wrap_fn { ( $( (fn $store_fn:ident, fn $store_at_fn:ident, $ty:ty => $wrapped:ty); )* ) => { $( #[doc = concat!("Executes a Wasmi `", stringify!($store_fn), "` instruction.")] /// /// # Errors /// /// - If `ptr + offset` overflows. /// - If `ptr + offset` stores out of bounds from `memory`. #[inline] pub fn $store_fn(memory: &mut [u8], ptr: u64, offset: u64, value: $ty) -> Result<(), TrapCode> { memory::store_wrap::<$ty, $wrapped>(memory, ptr, offset, value) } #[doc = concat!("Executes a Wasmi `", stringify!($store_at_fn), "` instruction.")] /// /// # Errors /// /// If `address` stores out of bounds from `memory`. #[inline] pub fn $store_at_fn(memory: &mut [u8], address: usize, value: $ty) -> Result<(), TrapCode> { memory::store_wrap_at::<$ty, $wrapped>(memory, address, value) } )* }; } gen_store_wrap_fn! { (fn i32_store8, fn i32_store8_at, i32 => i8); (fn i32_store16, fn i32_store16_at, i32 => i16); (fn i64_store8, fn i64_store8_at, i64 => i8); (fn i64_store16, fn i64_store16_at, i64 => i16); (fn i64_store32, fn i64_store32_at, i64 => i32); } macro_rules! gen_store_fn { ( $( (fn $store_fn:ident, fn $store_at_fn:ident, $ty:ty); )* ) => { $( #[doc = concat!("Executes a Wasmi `", stringify!($store_fn), "` instruction.")] /// /// # Errors /// /// - If `ptr + offset` overflows. /// - If `ptr + offset` stores out of bounds from `memory`. #[inline] pub fn $store_fn(memory: &mut [u8], ptr: u64, offset: u64, value: $ty) -> Result<(), TrapCode> { memory::store::<$ty>(memory, ptr, offset, value) } #[doc = concat!("Executes a Wasmi `", stringify!($store_at_fn), "` instruction.")] /// /// # Errors /// /// If `address` stores out of bounds from `memory`. #[inline] pub fn $store_at_fn(memory: &mut [u8], address: usize, value: $ty) -> Result<(), TrapCode> { memory::store_at::<$ty>(memory, address, value) } )* }; } gen_store_fn! { (fn store32, fn store32_at, u32); (fn store64, fn store64_at, u64); } /// Combines the two 64-bit `lo` and `hi` into a single `i128` value. fn combine128(lo: i64, hi: i64) -> i128 { let lo = i128::from(lo as u64); let hi = i128::from(hi as u64); (hi << 64) | lo } /// Splits the single `i128` value into a 64-bit `lo` and `hi` part. fn split128(value: i128) -> (i64, i64) { let hi = (value >> 64) as i64; let lo = value as i64; (lo, hi) } /// Execute an `i64.add128` Wasm instruction. /// /// Returns a pair of `(lo, hi)` 64-bit values representing the 128-bit result. /// /// # Note /// /// This instruction is part of the Wasm `wide-arithmetic` proposal. pub fn i64_add128(lhs_lo: i64, lhs_hi: i64, rhs_lo: i64, rhs_hi: i64) -> (i64, i64) { let lhs = combine128(lhs_lo, lhs_hi); let rhs = combine128(rhs_lo, rhs_hi); let result = lhs.wrapping_add(rhs); split128(result) } /// Execute an `i64.sub128` Wasm instruction. /// /// Returns a pair of `(lo, hi)` 64-bit values representing the 128-bit result. /// /// # Note /// /// This instruction is part of the Wasm `wide-arithmetic` proposal. pub fn i64_sub128(lhs_lo: i64, lhs_hi: i64, rhs_lo: i64, rhs_hi: i64) -> (i64, i64) { let lhs = combine128(lhs_lo, lhs_hi); let rhs = combine128(rhs_lo, rhs_hi); let result = lhs.wrapping_sub(rhs); split128(result) } /// Execute an `i64.mul_wide_s` Wasm instruction. /// /// Returns a pair of `(lo, hi)` 64-bit values representing the 128-bit result. /// /// # Note /// /// This instruction is part of the Wasm `wide-arithmetic` proposal. pub fn i64_mul_wide_s(lhs: i64, rhs: i64) -> (i64, i64) { let lhs = i128::from(lhs); let rhs = i128::from(rhs); let result = lhs.wrapping_mul(rhs); split128(result) } /// Execute an `i64.mul_wide_s` Wasm instruction. /// /// Returns a pair of `(lo, hi)` 64-bit values representing the 128-bit result. /// /// # Note /// /// This instruction is part of the Wasm `wide-arithmetic` proposal. pub fn i64_mul_wide_u(lhs: i64, rhs: i64) -> (i64, i64) { let lhs = u128::from(lhs as u64); let rhs = u128::from(rhs as u64); let result = lhs.wrapping_mul(rhs); split128(result as i128) }