Add no_std support (#122)

* add default-enabled std feature

* use parity-wasm/std feature only if std is enabled

* drop dependency on std::io

* use hashmap_core instead of std::collections::HashMap

* disable std::error in no_std

* core and alloc all the things

* mention no_std in readme

* add no_std feature and use hashmap_core only on no_std

* rename the no_std feature to core

* drop dependency on byteorder/std

* simplify float impl macro

* remove some trailing whitespace

* use libm for float math in no_std

* add note about no_std panics of libm to readme

* Embed nan-preserving-float crate.

* Add no_std check to the Travis CI config

* add missing dev-dependency
This commit is contained in:
Julius Rakow 2018-10-29 10:16:55 +00:00 committed by Sergey Pepyakin
parent 2f7505d120
commit 20154c5e24
23 changed files with 452 additions and 121 deletions

View File

@ -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: |

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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<FuncInstance>);
impl ::std::ops::Deref for FuncRef {
impl ::core::ops::Deref for FuncRef {
type Target = FuncInstance;
fn deref(&self) -> &FuncInstance {
&self.0

View File

@ -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<GlobalInstance>);
impl ::std::ops::Deref for GlobalRef {
impl ::core::ops::Deref for GlobalRef {
type Target = GlobalInstance;
fn deref(&self) -> &GlobalInstance {
&self.0

View File

@ -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::<Self>()

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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<MemoryInstance>);
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<T: LittleEndianConvert>(&self, offset: u32) -> Result<T, Error> {
let mut buffer = self.buffer.borrow_mut();
let region = self.checked_region(&mut buffer, offset as usize, ::std::mem::size_of::<T>())?;
let region = self.checked_region(&mut buffer, offset as usize, ::core::mem::size_of::<T>())?;
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<T: LittleEndianConvert>(&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::<T>())?.range();
let range = self.checked_region(&mut buffer, offset as usize, ::core::mem::size_of::<T>())?.range();
value.into_little_endian(&mut buffer[range]);
Ok(())
}
@ -254,7 +256,7 @@ impl MemoryInstance {
}
fn checked_region<B>(&self, buffer: &mut B, offset: usize, size: usize) -> Result<CheckedRegion, Error>
where B: ::std::ops::DerefMut<Target=Vec<u8>>
where B: ::core::ops::DerefMut<Target=Vec<u8>>
{
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<B>(&self, buffer: &mut B, offset1: usize, size1: usize, offset2: usize, size2: usize)
-> Result<(CheckedRegion, CheckedRegion), Error>
where B: ::std::ops::DerefMut<Target=Vec<u8>>
where B: ::core::ops::DerefMut<Target=Vec<u8>>
{
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,

View File

@ -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<ModuleInstance>);
impl ::std::ops::Deref for ModuleRef {
impl ::core::ops::Deref for ModuleRef {
type Target = ModuleInstance;
fn deref(&self) -> &ModuleInstance {
&self.0

212
src/nan_preserving_float.rs Normal file
View File

@ -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<T: Into<$for>> $op<T> 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<T: Into<$for> + Copy> PartialEq<T> for $for {
fn eq(&self, other: &T) -> bool {
$is::from(*self) == $is::from((*other).into())
}
}
impl<T: Into<$for> + Copy> PartialOrd<T> for $for {
fn partial_cmp(&self, other: &T) -> Option<Ordering> {
$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<u32> for F32 {
fn from(other: u32) -> Self {
Self::from_bits(other)
}
}
impl From<F32> for u32 {
fn from(other: F32) -> Self {
other.to_bits()
}
}
impl From<u64> for F64 {
fn from(other: u64) -> Self {
Self::from_bits(other)
}
}
impl From<F64> 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<T, F, I>(iter: I)
where
T: Add<Output = T>
+ Div<Output = T>
+ Mul<Output = T>
+ Sub<Output = T>
+ Neg<Output = T>
+ Copy
+ Debug
+ PartialEq,
F: Into<T>
+ Add<Output = F>
+ Div<Output = F>
+ Mul<Output = F>
+ Sub<Output = F>
+ Neg<Output = F>
+ Copy
+ Debug,
I: IntoIterator<Item = (F, F)>,
{
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::<F32, f32, _>(iter.take(1000));
}
#[test]
fn test_ops_f64() {
let mut rng = rand::thread_rng();
let iter = iter::repeat(()).map(|_| rng.gen());
test_ops::<F64, f64, _>(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);
}
}

View File

@ -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::<RuntimeValue>();
pub const DEFAULT_VALUE_STACK_LIMIT: usize = (1024 * 1024) / ::core::mem::size_of::<RuntimeValue>();
// 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<RuntimeValue>, externals: &'a mut E) -> Result<Option<RuntimeValue>, 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());

View File

@ -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<TableInstance>);
impl ::std::ops::Deref for TableRef {
impl ::core::ops::Deref for TableRef {
type Target = TableInstance;
fn deref(&self) -> &TableInstance {
&self.0

View File

@ -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)
}
}

View File

@ -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::<u32>().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::<u64>().unwrap(), overflow_i64);
}

View File

@ -1,4 +1,4 @@
use std::borrow::Cow;
use alloc::borrow::Cow;
use parity_wasm::elements::{
FunctionType, ValueType as EValueType, GlobalType, TableType, MemoryType};

View File

@ -1,3 +1,5 @@
#[allow(unused_imports)]
use alloc::prelude::*;
use parity_wasm::elements::{MemoryType, TableType, GlobalType, BlockType, ValueType, FunctionType};
use validation::Error;

View File

@ -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");

View File

@ -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

View File

@ -1,3 +1,5 @@
#[allow(unused_imports)]
use alloc::prelude::*;
use parity_wasm::elements::{Local, ValueType};
use validation::Error;

View File

@ -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<Self> {
@ -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<Self> {
@ -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<Self> {
@ -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<Self> {
@ -578,88 +577,86 @@ impl LittleEndianConvert for u8 {
}
impl LittleEndianConvert for i16 {
fn into_little_endian(self, mut buffer: &mut[u8]) {
buffer.write_i16::<LittleEndian>(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<Self, Error> {
io::Cursor::new(buffer).read_i16::<LittleEndian>()
.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::<LittleEndian>(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<Self, Error> {
io::Cursor::new(buffer).read_u16::<LittleEndian>()
.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::<LittleEndian>(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<Self, Error> {
io::Cursor::new(buffer).read_i32::<LittleEndian>()
.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::<LittleEndian>(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<Self, Error> {
io::Cursor::new(buffer).read_u32::<LittleEndian>()
.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::<LittleEndian>(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<Self, Error> {
io::Cursor::new(buffer).read_i64::<LittleEndian>()
.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::<LittleEndian>(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<Self, Error> {
io::Cursor::new(buffer).read_u32::<LittleEndian>()
.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::<LittleEndian>(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<Self, Error> {
io::Cursor::new(buffer).read_u64::<LittleEndian>()
.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);