diff --git a/.travis.yml b/.travis.yml index ab78915..13c84ce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,6 +23,8 @@ script: # Make sure nightly targets are not broken. - if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo check --tests --manifest-path=fuzz/Cargo.toml; fi - if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo check --benches --manifest-path=benches/Cargo.toml; fi +# Make sure `no_std` version checks. +- if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo +nightly check --no-default-features --features core; fi - ./test.sh - ./doc.sh after_success: | diff --git a/Cargo.toml b/Cargo.toml index e95ad17..7e7ebce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,12 +10,22 @@ description = "WebAssembly interpreter" keywords = ["wasm", "webassembly", "bytecode", "interpreter"] exclude = [ "/res/*", "/tests/*", "/fuzz/*", "/benches/*" ] +[features] +default = ["std"] +# Disable for no_std support +std = ["parity-wasm/std", "byteorder/std"] +# Enable for no_std support +# hashmap_core only works on no_std +core = ["hashmap_core", "libm"] + [dependencies] -parity-wasm = "0.31" -byteorder = "1.0" +parity-wasm = { version = "0.31", default-features = false } +byteorder = { version = "1.0", default-features = false } +hashmap_core = { version = "0.1.9", optional = true } memory_units = "0.3.0" -nan-preserving-float = "0.1.0" +libm = { version = "0.1.2", optional = true } [dev-dependencies] assert_matches = "1.1" +rand = "0.4.2" wabt = "0.6" diff --git a/README.md b/README.md index 8d2b2f5..a8c48fa 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,24 @@ cargo build cargo test ``` +# `no_std` support +This crate supports `no_std` environments. +Enable the `core` feature and disable default features: +```toml +[dependencies] +parity-wasm = { + version = "0.31", + default-features = false, + features = "core" +} +``` + +The `core` feature requires the `core` and `alloc` libraries and a nightly compiler. +Also, code related to `std::error` is disabled. + +Floating point operations in `no_std` use [`libm`](https://crates.io/crates/libm), which sometimes panics in debug mode (https://github.com/japaric/libm/issues/4). +So make sure to either use release builds or avoid WASM with floating point operations, for example by using [`deny_floating_point`](https://docs.rs/wasmi/0.4.0/wasmi/struct.Module.html#method.deny_floating_point). + ## Contribution Unless you explicitly state otherwise, any contribution intentionally submitted diff --git a/src/common/stack.rs b/src/common/stack.rs index 2366adb..a96af35 100644 --- a/src/common/stack.rs +++ b/src/common/stack.rs @@ -1,6 +1,9 @@ +#[allow(unused_imports)] +use alloc::prelude::*; +#[cfg(feature = "std")] use std::error; -use std::fmt; +use core::fmt; #[derive(Debug)] pub struct Error(String); @@ -11,6 +14,7 @@ impl fmt::Display for Error { } } +#[cfg(feature = "std")] impl error::Error for Error { fn description(&self) -> &str { &self.0 diff --git a/src/func.rs b/src/func.rs index 10162fa..802f16c 100644 --- a/src/func.rs +++ b/src/func.rs @@ -1,5 +1,7 @@ -use std::rc::{Rc, Weak}; -use std::fmt; +#[allow(unused_imports)] +use alloc::prelude::*; +use alloc::rc::{Rc, Weak}; +use core::fmt; use parity_wasm::elements::Local; use {Trap, TrapKind, Signature}; use host::Externals; @@ -17,7 +19,7 @@ use isa; #[derive(Clone, Debug)] pub struct FuncRef(Rc); -impl ::std::ops::Deref for FuncRef { +impl ::core::ops::Deref for FuncRef { type Target = FuncInstance; fn deref(&self) -> &FuncInstance { &self.0 diff --git a/src/global.rs b/src/global.rs index aec71ed..fe7e276 100644 --- a/src/global.rs +++ b/src/global.rs @@ -1,5 +1,5 @@ -use std::rc::Rc; -use std::cell::Cell; +use alloc::rc::Rc; +use core::cell::Cell; use value::RuntimeValue; use Error; use types::ValueType; @@ -13,7 +13,7 @@ use parity_wasm::elements::{ValueType as EValueType}; #[derive(Clone, Debug)] pub struct GlobalRef(Rc); -impl ::std::ops::Deref for GlobalRef { +impl ::core::ops::Deref for GlobalRef { type Target = GlobalInstance; fn deref(&self) -> &GlobalInstance { &self.0 diff --git a/src/host.rs b/src/host.rs index 65393e8..745f207 100644 --- a/src/host.rs +++ b/src/host.rs @@ -1,4 +1,4 @@ -use std::any::TypeId; +use core::any::TypeId; use value::{RuntimeValue, FromRuntimeValue}; use {TrapKind, Trap}; @@ -98,7 +98,7 @@ impl<'a> RuntimeArgs<'a> { /// _ => panic!(), /// } /// ``` -pub trait HostError: 'static + ::std::fmt::Display + ::std::fmt::Debug + Send + Sync { +pub trait HostError: 'static + ::core::fmt::Display + ::core::fmt::Debug + Send + Sync { #[doc(hidden)] fn __private_get_type_id__(&self) -> TypeId { TypeId::of::() diff --git a/src/imports.rs b/src/imports.rs index 5907cef..bb52eb4 100644 --- a/src/imports.rs +++ b/src/imports.rs @@ -1,4 +1,11 @@ +#[allow(unused_imports)] +use alloc::prelude::*; + +#[cfg(feature = "std")] use std::collections::HashMap; +#[cfg(not(feature = "std"))] +use hashmap_core::HashMap; + use global::GlobalRef; use memory::MemoryRef; use func::FuncRef; diff --git a/src/isa.rs b/src/isa.rs index 646db4a..b188843 100644 --- a/src/isa.rs +++ b/src/isa.rs @@ -67,6 +67,9 @@ //! - Reserved immediates are ignored for `call_indirect`, `current_memory`, `grow_memory`. //! +#[allow(unused_imports)] +use alloc::prelude::*; + /// Should we keep a value before "discarding" a stack frame? /// /// Note that this is a `enum` since Wasm doesn't support multiple return diff --git a/src/lib.rs b/src/lib.rs index 104b7e0..4c3221d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,6 +96,21 @@ #![warn(missing_docs)] +#![cfg_attr(not(feature = "std"), no_std)] + +//// alloc is required in no_std +#![cfg_attr(not(feature = "std"), feature(alloc))] + +#[cfg(not(feature = "std"))] +#[macro_use] +extern crate alloc; +#[cfg(feature = "std")] +extern crate std as alloc; + +#[cfg(feature = "std")] +#[macro_use] +extern crate core; + #[cfg(test)] extern crate wabt; #[cfg(test)] @@ -104,13 +119,19 @@ extern crate assert_matches; extern crate parity_wasm; extern crate byteorder; +#[cfg(not(feature = "std"))] +extern crate hashmap_core; extern crate memory_units as memory_units_crate; -pub extern crate nan_preserving_float; - -use std::fmt; +#[allow(unused_imports)] +use alloc::prelude::*; +use core::fmt; +#[cfg(feature = "std")] use std::error; +#[cfg(not(feature = "std"))] +extern crate libm; + /// Error type which can be thrown by wasm code or by host environment. /// /// Under some conditions, wasm execution may produce a `Trap`, which immediately aborts execution. @@ -138,6 +159,7 @@ impl fmt::Display for Trap { } } +#[cfg(feature = "std")] impl error::Error for Trap { fn description(&self) -> &str { "runtime trap" @@ -308,6 +330,7 @@ impl fmt::Display for Error { } } +#[cfg(feature = "std")] impl error::Error for Error { fn description(&self) -> &str { match *self { @@ -367,6 +390,7 @@ mod global; mod func; mod types; mod isa; +pub mod nan_preserving_float; #[cfg(test)] mod tests; diff --git a/src/memory.rs b/src/memory.rs index 730139c..c25f335 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -1,9 +1,11 @@ -use std::u32; -use std::ops::Range; -use std::cmp; -use std::fmt; -use std::rc::Rc; -use std::cell::{Cell, RefCell}; +#[allow(unused_imports)] +use alloc::prelude::*; +use alloc::rc::Rc; +use core::u32; +use core::ops::Range; +use core::cmp; +use core::fmt; +use core::cell::{Cell, RefCell}; use parity_wasm::elements::ResizableLimits; use Error; use memory_units::{RoundUpTo, Pages, Bytes}; @@ -28,7 +30,7 @@ const LINEAR_MEMORY_MAX_PAGES: Pages = Pages(65536); #[derive(Clone, Debug)] pub struct MemoryRef(Rc); -impl ::std::ops::Deref for MemoryRef { +impl ::core::ops::Deref for MemoryRef { type Target = MemoryInstance; fn deref(&self) -> &MemoryInstance { &self.0 @@ -172,7 +174,7 @@ impl MemoryInstance { /// Get value from memory at given offset. pub fn get_value(&self, offset: u32) -> Result { let mut buffer = self.buffer.borrow_mut(); - let region = self.checked_region(&mut buffer, offset as usize, ::std::mem::size_of::())?; + let region = self.checked_region(&mut buffer, offset as usize, ::core::mem::size_of::())?; Ok(T::from_little_endian(&buffer[region.range()]).expect("Slice size is checked")) } @@ -216,7 +218,7 @@ impl MemoryInstance { /// Copy value in the memory at given offset. pub fn set_value(&self, offset: u32, value: T) -> Result<(), Error> { let mut buffer = self.buffer.borrow_mut(); - let range = self.checked_region(&mut buffer, offset as usize, ::std::mem::size_of::())?.range(); + let range = self.checked_region(&mut buffer, offset as usize, ::core::mem::size_of::())?.range(); value.into_little_endian(&mut buffer[range]); Ok(()) } @@ -254,7 +256,7 @@ impl MemoryInstance { } fn checked_region(&self, buffer: &mut B, offset: usize, size: usize) -> Result - where B: ::std::ops::DerefMut> + where B: ::core::ops::DerefMut> { let end = offset.checked_add(size) .ok_or_else(|| Error::Memory(format!("trying to access memory block of size {} from offset {}", size, offset)))?; @@ -275,7 +277,7 @@ impl MemoryInstance { fn checked_region_pair(&self, buffer: &mut B, offset1: usize, size1: usize, offset2: usize, size2: usize) -> Result<(CheckedRegion, CheckedRegion), Error> - where B: ::std::ops::DerefMut> + where B: ::core::ops::DerefMut> { let end1 = offset1.checked_add(size1) .ok_or_else(|| Error::Memory(format!("trying to access memory block of size {} from offset {}", size1, offset1)))?; @@ -314,7 +316,7 @@ impl MemoryInstance { let (read_region, write_region) = self.checked_region_pair(&mut buffer, src_offset, len, dst_offset, len)?; - unsafe { ::std::ptr::copy( + unsafe { ::core::ptr::copy( buffer[read_region.range()].as_ptr(), buffer[write_region.range()].as_mut_ptr(), len, @@ -343,7 +345,7 @@ impl MemoryInstance { return Err(Error::Memory(format!("non-overlapping copy is used for overlapping regions"))) } - unsafe { ::std::ptr::copy_nonoverlapping( + unsafe { ::core::ptr::copy_nonoverlapping( buffer[read_region.range()].as_ptr(), buffer[write_region.range()].as_mut_ptr(), len, diff --git a/src/module.rs b/src/module.rs index a61e866..4718bc5 100644 --- a/src/module.rs +++ b/src/module.rs @@ -1,9 +1,16 @@ +#[allow(unused_imports)] +use alloc::prelude::*; +use alloc::rc::Rc; use runner::check_function_args; use Trap; -use std::rc::Rc; -use std::cell::RefCell; -use std::fmt; +use core::cell::RefCell; +use core::fmt; + +#[cfg(feature = "std")] use std::collections::HashMap; +#[cfg(not(feature = "std"))] +use hashmap_core::HashMap; + use parity_wasm::elements::{External, InitExpr, Internal, Instruction, ResizableLimits, Type}; use {Module, Error, Signature, MemoryInstance, RuntimeValue, TableInstance}; use imports::ImportResolver; @@ -32,7 +39,7 @@ use memory_units::Pages; #[derive(Clone, Debug)] pub struct ModuleRef(pub(crate) Rc); -impl ::std::ops::Deref for ModuleRef { +impl ::core::ops::Deref for ModuleRef { type Target = ModuleInstance; fn deref(&self) -> &ModuleInstance { &self.0 diff --git a/src/nan_preserving_float.rs b/src/nan_preserving_float.rs new file mode 100644 index 0000000..6579662 --- /dev/null +++ b/src/nan_preserving_float.rs @@ -0,0 +1,212 @@ +#![allow(missing_docs)] + +#[cfg(not(feature = "std"))] +use libm::{F32Ext, F64Ext}; + +use core::ops::{Add, Div, Mul, Neg, Sub, Rem}; +use core::cmp::{Ordering, PartialEq, PartialOrd}; + +macro_rules! impl_binop { + ($for:ident, $is:ident, $op:ident, $func_name:ident) => { + impl> $op for $for { + type Output = Self; + + fn $func_name(self, other: T) -> Self { + $for( + $op::$func_name( + $is::from_bits(self.0), + $is::from_bits(other.into().0) + ).to_bits() + ) + } + } + } +} + +macro_rules! float { + ($for:ident, $rep:ident, $is:ident) => { + float!($for, $rep, $is, 1 << (::core::mem::size_of::<$is>() * 8 - 1)); + }; + ($for:ident, $rep:ident, $is:ident, $sign_bit:expr) => { + #[derive(Copy, Clone)] + pub struct $for($rep); + + impl_binop!($for, $is, Add, add); + impl_binop!($for, $is, Sub, sub); + impl_binop!($for, $is, Mul, mul); + impl_binop!($for, $is, Div, div); + impl_binop!($for, $is, Rem, rem); + + impl $for { + pub fn from_bits(other: $rep) -> Self { + $for(other) + } + + pub fn to_bits(self) -> $rep { + self.0 + } + + pub fn from_float(fl: $is) -> Self { + fl.into() + } + + pub fn to_float(self) -> $is { + self.into() + } + + pub fn is_nan(self) -> bool { + self.to_float().is_nan() + } + + pub fn abs(self) -> Self { + $for(self.0 & !$sign_bit) + } + + pub fn fract(self) -> Self { + self.to_float().fract().into() + } + + pub fn min(self, other: Self) -> Self { + Self::from(self.to_float().min(other.to_float())) + } + + pub fn max(self, other: Self) -> Self { + Self::from(self.to_float().max(other.to_float())) + } + } + + impl From<$is> for $for { + fn from(other: $is) -> $for { + $for(other.to_bits()) + } + } + + impl From<$for> for $is { + fn from(other: $for) -> $is { + <$is>::from_bits(other.0) + } + } + + impl Neg for $for { + type Output = Self; + + fn neg(self) -> Self { + $for(self.0 ^ $sign_bit) + } + } + + impl + Copy> PartialEq for $for { + fn eq(&self, other: &T) -> bool { + $is::from(*self) == $is::from((*other).into()) + } + } + + impl + Copy> PartialOrd for $for { + fn partial_cmp(&self, other: &T) -> Option { + $is::from(*self).partial_cmp(&$is::from((*other).into())) + } + } + + impl ::core::fmt::Debug for $for { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + $is::from(*self).fmt(f) + } + } + } +} + +float!(F32, u32, f32); +float!(F64, u64, f64); + +impl From for F32 { + fn from(other: u32) -> Self { + Self::from_bits(other) + } +} + +impl From for u32 { + fn from(other: F32) -> Self { + other.to_bits() + } +} + +impl From for F64 { + fn from(other: u64) -> Self { + Self::from_bits(other) + } +} + +impl From for u64 { + fn from(other: F64) -> Self { + other.to_bits() + } +} + +#[cfg(test)] +mod tests { + extern crate rand; + + use self::rand::Rng; + + use super::{F32, F64}; + + use core::ops::{Add, Div, Mul, Neg, Sub}; + use core::fmt::Debug; + use core::iter; + + fn test_ops(iter: I) + where + T: Add + + Div + + Mul + + Sub + + Neg + + Copy + + Debug + + PartialEq, + F: Into + + Add + + Div + + Mul + + Sub + + Neg + + Copy + + Debug, + I: IntoIterator, + { + for (a, b) in iter { + assert_eq!((a + b).into(), a.into() + b.into()); + assert_eq!((a - b).into(), a.into() - b.into()); + assert_eq!((a * b).into(), a.into() * b.into()); + assert_eq!((a / b).into(), a.into() / b.into()); + assert_eq!((-a).into(), -a.into()); + assert_eq!((-b).into(), -b.into()); + } + } + + #[test] + fn test_ops_f32() { + let mut rng = rand::thread_rng(); + let iter = iter::repeat(()).map(|_| rng.gen()); + + test_ops::(iter.take(1000)); + } + + #[test] + fn test_ops_f64() { + let mut rng = rand::thread_rng(); + let iter = iter::repeat(()).map(|_| rng.gen()); + + test_ops::(iter.take(1000)); + } + + #[test] + fn test_neg_nan_f32() { + assert_eq!((-F32(0xff80_3210)).0, 0x7f80_3210); + } + + #[test] + fn test_neg_nan_f64() { + assert_eq!((-F64(0xff80_3210_0000_0000)).0, 0x7f80_3210_0000_0000); + } +} diff --git a/src/runner.rs b/src/runner.rs index d2b1ce4..fcce6c5 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -1,7 +1,9 @@ -use std::ops; -use std::{u32, usize}; -use std::fmt; -use std::iter::repeat; +#[allow(unused_imports)] +use alloc::prelude::*; +use core::ops; +use core::{u32, usize}; +use core::fmt; +use core::iter::repeat; use parity_wasm::elements::Local; use {Error, Trap, TrapKind, Signature}; use module::ModuleRef; @@ -19,7 +21,7 @@ use nan_preserving_float::{F32, F64}; use isa; /// Maximum number of entries in value stack. -pub const DEFAULT_VALUE_STACK_LIMIT: usize = (1024 * 1024) / ::std::mem::size_of::(); +pub const DEFAULT_VALUE_STACK_LIMIT: usize = (1024 * 1024) / ::core::mem::size_of::(); // TODO: Make these parameters changeble. pub const DEFAULT_CALL_STACK_LIMIT: usize = 64 * 1024; @@ -122,7 +124,7 @@ impl Interpreter { } pub fn resume_execution<'a, E: Externals + 'a>(&mut self, return_val: Option, externals: &'a mut E) -> Result, Trap> { - use std::mem::swap; + use core::mem::swap; // Ensure that the VM is resumable. This is checked in `FuncInvocation::resume_execution`. assert!(self.state.is_resumable()); diff --git a/src/table.rs b/src/table.rs index 53dcc10..0e42196 100644 --- a/src/table.rs +++ b/src/table.rs @@ -1,7 +1,9 @@ -use std::u32; -use std::fmt; -use std::cell::RefCell; -use std::rc::Rc; +#[allow(unused_imports)] +use alloc::prelude::*; +use alloc::rc::Rc; +use core::u32; +use core::fmt; +use core::cell::RefCell; use parity_wasm::elements::ResizableLimits; use Error; use func::FuncRef; @@ -16,7 +18,7 @@ use module::check_limits; #[derive(Clone, Debug)] pub struct TableRef(Rc); -impl ::std::ops::Deref for TableRef { +impl ::core::ops::Deref for TableRef { type Target = TableInstance; fn deref(&self) -> &TableInstance { &self.0 diff --git a/src/tests/host.rs b/src/tests/host.rs index c0b5916..4ba09d2 100644 --- a/src/tests/host.rs +++ b/src/tests/host.rs @@ -12,8 +12,8 @@ struct HostErrorWithCode { error_code: u32, } -impl ::std::fmt::Display for HostErrorWithCode { - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { +impl ::core::fmt::Display for HostErrorWithCode { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> Result<(), ::core::fmt::Error> { write!(f, "{}", self.error_code) } } diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 3ad8ba8..193e274 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -23,10 +23,10 @@ fn assert_error_properties() { fn unsigned_to_runtime_value() { use super::RuntimeValue; - let overflow_i32: u32 = ::std::i32::MAX as u32 + 1; + let overflow_i32: u32 = ::core::i32::MAX as u32 + 1; assert_eq!(RuntimeValue::from(overflow_i32).try_into::().unwrap(), overflow_i32); - let overflow_i64: u64 = ::std::i64::MAX as u64 + 1; + let overflow_i64: u64 = ::core::i64::MAX as u64 + 1; assert_eq!(RuntimeValue::from(overflow_i64).try_into::().unwrap(), overflow_i64); } diff --git a/src/types.rs b/src/types.rs index de5e673..6cf139a 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,4 +1,4 @@ -use std::borrow::Cow; +use alloc::borrow::Cow; use parity_wasm::elements::{ FunctionType, ValueType as EValueType, GlobalType, TableType, MemoryType}; diff --git a/src/validation/context.rs b/src/validation/context.rs index 145ac18..19805e5 100644 --- a/src/validation/context.rs +++ b/src/validation/context.rs @@ -1,3 +1,5 @@ +#[allow(unused_imports)] +use alloc::prelude::*; use parity_wasm::elements::{MemoryType, TableType, GlobalType, BlockType, ValueType, FunctionType}; use validation::Error; diff --git a/src/validation/func.rs b/src/validation/func.rs index f7a975e..02ef454 100644 --- a/src/validation/func.rs +++ b/src/validation/func.rs @@ -1,4 +1,6 @@ -use std::u32; +#[allow(unused_imports)] +use alloc::prelude::*; +use core::u32; use parity_wasm::elements::{Instruction, BlockType, ValueType, TableElementType, Func, FuncBody}; use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX}; use validation::context::ModuleContext; @@ -1708,7 +1710,7 @@ impl Sink { } fn emit_br_table(&mut self, targets: &[Target], default: Target) { - use std::iter; + use core::iter; let pc = self.cur_pc(); let mut isa_targets = Vec::new(); @@ -1739,7 +1741,7 @@ impl Sink { /// /// Panics if the label is already resolved. fn resolve_label(&mut self, label: LabelId) { - use std::mem; + use core::mem; if let (Label::Resolved(_), _) = self.labels[label.0] { panic!("Trying to resolve already resolved label"); diff --git a/src/validation/mod.rs b/src/validation/mod.rs index d6e452f..fdb2d11 100644 --- a/src/validation/mod.rs +++ b/src/validation/mod.rs @@ -1,6 +1,14 @@ +#[allow(unused_imports)] +use alloc::prelude::*; +#[cfg(feature = "std")] use std::error; -use std::fmt; +use core::fmt; + +#[cfg(feature = "std")] use std::collections::HashSet; +#[cfg(not(feature = "std"))] +use hashmap_core::HashSet; + use parity_wasm::elements::{ BlockType, External, GlobalEntry, GlobalType, Internal, MemoryType, Module, Instruction, ResizableLimits, TableType, ValueType, InitExpr, Type, @@ -27,6 +35,7 @@ impl fmt::Display for Error { } } +#[cfg(feature = "std")] impl error::Error for Error { fn description(&self) -> &str { &self.0 @@ -45,7 +54,7 @@ pub struct ValidatedModule { pub module: Module, } -impl ::std::ops::Deref for ValidatedModule { +impl ::core::ops::Deref for ValidatedModule { type Target = Module; fn deref(&self) -> &Module { &self.module diff --git a/src/validation/util.rs b/src/validation/util.rs index acdec1b..69d70ac 100644 --- a/src/validation/util.rs +++ b/src/validation/util.rs @@ -1,3 +1,5 @@ +#[allow(unused_imports)] +use alloc::prelude::*; use parity_wasm::elements::{Local, ValueType}; use validation::Error; diff --git a/src/value.rs b/src/value.rs index 88b424d..87eabb5 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,7 +1,6 @@ -use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use byteorder::{ByteOrder, LittleEndian}; use nan_preserving_float::{F32, F64}; -use std::io; -use std::{f32, i32, i64, u32, u64}; +use core::{f32, i32, i64, u32, u64}; use TrapKind; #[derive(Debug)] @@ -261,7 +260,7 @@ impl FromRuntimeValue for bool { } /// This conversion assumes that `i8` is represented as an [`I32`]. -/// +/// /// [`I32`]: enum.RuntimeValue.html#variant.I32 impl FromRuntimeValue for i8 { fn from_runtime_value(val: RuntimeValue) -> Option { @@ -275,7 +274,7 @@ impl FromRuntimeValue for i8 { } /// This conversion assumes that `i16` is represented as an [`I32`]. -/// +/// /// [`I32`]: enum.RuntimeValue.html#variant.I32 impl FromRuntimeValue for i16 { fn from_runtime_value(val: RuntimeValue) -> Option { @@ -289,7 +288,7 @@ impl FromRuntimeValue for i16 { } /// This conversion assumes that `u8` is represented as an [`I32`]. -/// +/// /// [`I32`]: enum.RuntimeValue.html#variant.I32 impl FromRuntimeValue for u8 { fn from_runtime_value(val: RuntimeValue) -> Option { @@ -303,7 +302,7 @@ impl FromRuntimeValue for u8 { } /// This conversion assumes that `u16` is represented as an [`I32`]. -/// +/// /// [`I32`]: enum.RuntimeValue.html#variant.I32 impl FromRuntimeValue for u16 { fn from_runtime_value(val: RuntimeValue) -> Option { @@ -578,88 +577,86 @@ impl LittleEndianConvert for u8 { } impl LittleEndianConvert for i16 { - fn into_little_endian(self, mut buffer: &mut[u8]) { - buffer.write_i16::(self) - .expect("i16 is written without any errors"); + fn into_little_endian(self, buffer: &mut[u8]) { + LittleEndian::write_i16(buffer, self); } fn from_little_endian(buffer: &[u8]) -> Result { - io::Cursor::new(buffer).read_i16::() - .map_err(|_| Error::InvalidLittleEndianBuffer) + buffer.get(0..2) + .map(LittleEndian::read_i16) + .ok_or_else(|| Error::InvalidLittleEndianBuffer) } } impl LittleEndianConvert for u16 { - fn into_little_endian(self, mut buffer: &mut[u8]) { - buffer.write_u16::(self) - .expect("u16 is written without any errors"); + fn into_little_endian(self, buffer: &mut[u8]) { + LittleEndian::write_u16(buffer, self); } fn from_little_endian(buffer: &[u8]) -> Result { - io::Cursor::new(buffer).read_u16::() - .map_err(|_| Error::InvalidLittleEndianBuffer) + buffer.get(0..2) + .map(LittleEndian::read_u16) + .ok_or_else(|| Error::InvalidLittleEndianBuffer) } } impl LittleEndianConvert for i32 { - fn into_little_endian(self, mut buffer: &mut[u8]) { - buffer.write_i32::(self) - .expect("i32 is written without any errors"); + fn into_little_endian(self, buffer: &mut[u8]) { + LittleEndian::write_i32(buffer, self); } fn from_little_endian(buffer: &[u8]) -> Result { - io::Cursor::new(buffer).read_i32::() - .map_err(|_| Error::InvalidLittleEndianBuffer) + buffer.get(0..4) + .map(LittleEndian::read_i32) + .ok_or_else(|| Error::InvalidLittleEndianBuffer) } } impl LittleEndianConvert for u32 { - fn into_little_endian(self, mut buffer: &mut[u8]) { - buffer.write_u32::(self) - .expect("u32 is written without any errors"); + fn into_little_endian(self, buffer: &mut[u8]) { + LittleEndian::write_u32(buffer, self); } fn from_little_endian(buffer: &[u8]) -> Result { - io::Cursor::new(buffer).read_u32::() - .map_err(|_| Error::InvalidLittleEndianBuffer) + buffer.get(0..4) + .map(LittleEndian::read_u32) + .ok_or_else(|| Error::InvalidLittleEndianBuffer) } } impl LittleEndianConvert for i64 { - fn into_little_endian(self, mut buffer: &mut[u8]) { - buffer.write_i64::(self) - .expect("i64 is written without any errors"); + fn into_little_endian(self, buffer: &mut[u8]) { + LittleEndian::write_i64(buffer, self); } fn from_little_endian(buffer: &[u8]) -> Result { - io::Cursor::new(buffer).read_i64::() - .map_err(|_| Error::InvalidLittleEndianBuffer) + buffer.get(0..8) + .map(LittleEndian::read_i64) + .ok_or_else(|| Error::InvalidLittleEndianBuffer) } } impl LittleEndianConvert for f32 { - fn into_little_endian(self, mut buffer: &mut[u8]) { - buffer.write_f32::(self) - .expect("f32 is written without any errors"); + fn into_little_endian(self, buffer: &mut[u8]) { + LittleEndian::write_f32(buffer, self); } fn from_little_endian(buffer: &[u8]) -> Result { - io::Cursor::new(buffer).read_u32::() - .map(f32::from_bits) - .map_err(|_| Error::InvalidLittleEndianBuffer) + buffer.get(0..4) + .map(LittleEndian::read_f32) + .ok_or_else(|| Error::InvalidLittleEndianBuffer) } } impl LittleEndianConvert for f64 { - fn into_little_endian(self, mut buffer: &mut[u8]) { - buffer.write_f64::(self) - .expect("i64 is written without any errors"); + fn into_little_endian(self, buffer: &mut[u8]) { + LittleEndian::write_f64(buffer, self); } fn from_little_endian(buffer: &[u8]) -> Result { - io::Cursor::new(buffer).read_u64::() - .map(f64::from_bits) - .map_err(|_| Error::InvalidLittleEndianBuffer) + buffer.get(0..8) + .map(LittleEndian::read_f64) + .ok_or_else(|| Error::InvalidLittleEndianBuffer) } } @@ -748,34 +745,52 @@ impl_integer!(u32); impl_integer!(i64); impl_integer!(u64); -macro_rules! impl_float { - ($type:ident, $int_type:ident) => { - impl_float!($type, $type, $int_type); +// Use std float functions in std environment. +// And libm's implementation in no_std +#[cfg(feature = "std")] +macro_rules! call_math { + ($op:ident, $e:expr, $fXX:ident, $FXXExt:ident) => { + $fXX::$op($e) }; - ($type:ident, $intermediate:ident, $int_type:ident) => { +} +#[cfg(not(feature = "std"))] +macro_rules! call_math { + ($op:ident, $e:expr, $fXX:ident, $FXXExt:ident) => { + ::libm::$FXXExt::$op($e) + }; +} + +// We cannot call the math functions directly, because there are multiple available implementaitons in no_std. +// In std, there are only `Value::$op` and `std::$fXX:$op`. +// The `std` ones are preferred, because they are not from a trait. +// For `no_std`, the implementations are `Value::$op` and `libm::FXXExt::$op`, +// both of which are trait implementations and hence ambiguous. +// So we have to use a full path, which is what `call_math!` does. +macro_rules! impl_float { + ($type:ident, $fXX:ident, $FXXExt:ident, $iXX:ident) => { impl Float<$type> for $type { fn abs(self) -> $type { - $intermediate::abs(self.into()).into() + call_math!(abs, $fXX::from(self), $fXX, $FXXExt).into() } fn floor(self) -> $type { - $intermediate::floor(self.into()).into() + call_math!(floor, $fXX::from(self), $fXX, $FXXExt).into() } fn ceil(self) -> $type { - $intermediate::ceil(self.into()).into() + call_math!(ceil, $fXX::from(self), $fXX, $FXXExt).into() } fn trunc(self) -> $type { - $intermediate::trunc(self.into()).into() + call_math!(trunc, $fXX::from(self), $fXX, $FXXExt).into() } fn round(self) -> $type { - $intermediate::round(self.into()).into() + call_math!(round, $fXX::from(self), $fXX, $FXXExt).into() } fn nearest(self) -> $type { let round = self.round(); - if self.fract().abs() != 0.5 { + if call_math!(fract, $fXX::from(self), $fXX, $FXXExt).abs() != 0.5 { return round; } - use std::ops::Rem; + use core::ops::Rem; if round.rem(2.0) == 1.0 { self.floor() } else if round.rem(2.0) == -1.0 { @@ -785,34 +800,40 @@ macro_rules! impl_float { } } fn sqrt(self) -> $type { - $intermediate::sqrt(self.into()).into() + call_math!(sqrt, $fXX::from(self), $fXX, $FXXExt).into() } // This instruction corresponds to what is sometimes called "minNaN" in other languages. fn min(self, other: $type) -> $type { - if self.is_nan() || other.is_nan() { - return ::std::$intermediate::NAN.into(); + if self.is_nan() { + return self; + } + if other.is_nan() { + return other; } self.min(other) } // This instruction corresponds to what is sometimes called "maxNaN" in other languages. fn max(self, other: $type) -> $type { - if self.is_nan() || other.is_nan() { - return ::std::$intermediate::NAN.into(); + if self.is_nan() { + return self; + } + if other.is_nan() { + return other; } self.max(other) } fn copysign(self, other: $type) -> $type { - use std::mem::size_of; + use core::mem::size_of; if self.is_nan() { return self; } - let sign_mask: $int_type = 1 << ((size_of::<$int_type>() << 3) - 1); - let self_int: $int_type = self.transmute_into(); - let other_int: $int_type = other.transmute_into(); + let sign_mask: $iXX = 1 << ((size_of::<$iXX>() << 3) - 1); + let self_int: $iXX = self.transmute_into(); + let other_int: $iXX = other.transmute_into(); let is_self_sign_set = (self_int & sign_mask) != 0; let is_other_sign_set = (other_int & sign_mask) != 0; if is_self_sign_set == is_other_sign_set { @@ -827,7 +848,7 @@ macro_rules! impl_float { }; } -impl_float!(f32, i32); -impl_float!(f64, i64); -impl_float!(F32, f32, i32); -impl_float!(F64, f64, i64); +impl_float!(f32, f32, F32Ext, i32); +impl_float!(f64, f64, F64Ext, i64); +impl_float!(F32, f32, F32Ext, i32); +impl_float!(F64, f64, F64Ext, i64);