Skip to content

Upgrade libm and make no-std config testable #241

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Nov 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ script:
# Check that `vec_memory` feature works.
- cargo check --features vec_memory
- travis_wait 60 ./test.sh
- TEST_NO_STD=1 travis_wait 60 ./test.sh
- ./doc.sh

after_success: |
Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ exclude = [ "/res/*", "/tests/*", "/fuzz/*", "/benches/*" ]
validation = { package = "wasmi-validation", version = "0.3", path = "validation", default-features = false }
parity-wasm = { version = "0.41.0", default-features = false }
memory_units = "0.3.0"
libm = { version = "0.1.2", optional = true }
libm = { version = "0.2.1", optional = true }
num-rational = { version = "0.2.2", default-features = false }
num-traits = { version = "0.2.8", default-features = false }
libc = { version = "0.2.58", optional = true}
Expand All @@ -41,7 +41,7 @@ std = [
]
# Enable for no_std support
core = [
# `core` doesn't support vec_memory
# `core` doesn't support mmaped memory
"vec_memory",
"validation/core",
"libm"
Expand Down
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@ wasmi = {

When the `core` feature is enabled, 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).

# License

`wasmi` is primarily distributed under the terms of both the MIT
Expand Down
15 changes: 13 additions & 2 deletions examples/invoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ extern crate wasmi;

use std::env::args;

use parity_wasm::elements::{External, FunctionType, Internal, Type, ValueType};
use parity_wasm::elements::{External, FunctionType, Internal, Module, Type, ValueType};
use wasmi::{ImportsBuilder, ModuleInstance, NopExternals, RuntimeValue};

fn main() {
Expand All @@ -15,7 +15,7 @@ fn main() {
let func_name = &args[2];
let (_, program_args) = args.split_at(3);

let module = parity_wasm::deserialize_file(&args[1]).expect("File to be deserialized");
let module = load_module(&args[1]);

// Extracts call arguments from command-line arguments
let args = {
Expand Down Expand Up @@ -118,3 +118,14 @@ fn main() {
.expect("")
);
}

#[cfg(feature = "std")]
fn load_module(file: &str) -> Module {
parity_wasm::deserialize_file(file).expect("File to be deserialized")
}

#[cfg(not(feature = "std"))]
fn load_module(file: &str) -> Module {
let mut buf = std::fs::read(file).expect("Read file");
parity_wasm::deserialize_buffer(&mut buf).expect("Deserialize module")
}
2 changes: 1 addition & 1 deletion src/memory/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ mod tests {
use super::{MemoryInstance, MemoryRef, LINEAR_MEMORY_PAGE_SIZE};
use crate::memory_units::Pages;
use crate::Error;
use std::rc::Rc;
use alloc::rc::Rc;

#[test]
fn alloc() {
Expand Down
3 changes: 1 addition & 2 deletions src/memory/vec_bytebuf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ pub struct ByteBuf {

impl ByteBuf {
pub fn new(len: usize) -> Result<Self, String> {
let mut buf = Vec::new();
buf.resize(len, 0u8);
let buf = vec![0; len];
Ok(Self { buf })
}

Expand Down
6 changes: 6 additions & 0 deletions src/prepare/tests.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
// Test-only code importing std for no-std testing
extern crate std;

use alloc::vec::Vec;
use std::println;

use super::{compile_module, CompiledModule};
use crate::isa;
use parity_wasm::{deserialize_buffer, elements::Module};
Expand Down
5 changes: 5 additions & 0 deletions src/tests/host.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Test-only code importing std for no-std testing
extern crate std;

use super::parse_wat;
use crate::memory_units::Pages;
use crate::types::ValueType;
Expand All @@ -6,6 +9,8 @@ use crate::{
MemoryInstance, MemoryRef, ModuleImportResolver, ModuleInstance, ModuleRef, ResumableError,
RuntimeArgs, RuntimeValue, Signature, TableDescriptor, TableInstance, TableRef, Trap, TrapKind,
};
use alloc::boxed::Box;
use std::println;

#[derive(Debug, Clone, PartialEq)]
struct HostErrorWithCode {
Expand Down
3 changes: 3 additions & 0 deletions src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ use super::Error;

fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
#[cfg(feature = "std")]
fn assert_std_err_impl<T: ::std::error::Error>() {}
#[cfg(not(feature = "std"))]
fn assert_std_err_impl<T>() {}

#[test]
fn assert_error_properties() {
Expand Down
4 changes: 4 additions & 0 deletions src/tests/wasm.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
// Test-only code importing std for no-std testing
extern crate std;

use crate::memory_units::Pages;
use crate::{
Error, FuncRef, GlobalDescriptor, GlobalInstance, GlobalRef, ImportsBuilder, MemoryDescriptor,
MemoryInstance, MemoryRef, Module, ModuleImportResolver, ModuleInstance, NopExternals,
RuntimeValue, Signature, TableDescriptor, TableInstance, TableRef,
};
use alloc::vec::Vec;
use std::fs::File;

struct Env {
Expand Down
113 changes: 85 additions & 28 deletions src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -828,48 +828,42 @@ impl_integer!(u32);
impl_integer!(i64);
impl_integer!(u64);

// 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)
};
mod fmath {
pub use f32;
pub use f64;
}

#[cfg(not(feature = "std"))]
macro_rules! call_math {
($op:ident, $e:expr, $fXX:ident, $FXXExt:ident) => {
::libm::$FXXExt::$op($e)
};
mod fmath {
pub use super::libm_adapters::f32;
pub use super::libm_adapters::f64;
}

// 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.
// 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 {
($type:ident, $fXX:ident, $FXXExt:ident, $iXX:ident) => {
($type:ident, $fXX:ident, $iXX:ident) => {
impl Float<$type> for $type {
fn abs(self) -> $type {
call_math!(abs, $fXX::from(self), $fXX, $FXXExt).into()
fmath::$fXX::abs($fXX::from(self)).into()
}
fn floor(self) -> $type {
call_math!(floor, $fXX::from(self), $fXX, $FXXExt).into()
fmath::$fXX::floor($fXX::from(self)).into()
}
fn ceil(self) -> $type {
call_math!(ceil, $fXX::from(self), $fXX, $FXXExt).into()
fmath::$fXX::ceil($fXX::from(self)).into()
}
fn trunc(self) -> $type {
call_math!(trunc, $fXX::from(self), $fXX, $FXXExt).into()
fmath::$fXX::trunc($fXX::from(self)).into()
}
fn round(self) -> $type {
call_math!(round, $fXX::from(self), $fXX, $FXXExt).into()
fmath::$fXX::round($fXX::from(self)).into()
}
fn nearest(self) -> $type {
let round = self.round();
if call_math!(fract, $fXX::from(self), $fXX, $FXXExt).abs() != 0.5 {
if fmath::$fXX::fract($fXX::from(self)).abs() != 0.5 {
return round;
}

Expand All @@ -883,7 +877,7 @@ macro_rules! impl_float {
}
}
fn sqrt(self) -> $type {
call_math!(sqrt, $fXX::from(self), $fXX, $FXXExt).into()
fmath::$fXX::sqrt($fXX::from(self)).into()
}
// This instruction corresponds to what is sometimes called "minNaN" in other languages.
fn min(self, other: $type) -> $type {
Expand Down Expand Up @@ -931,7 +925,70 @@ macro_rules! impl_float {
};
}

impl_float!(f32, f32, F32Ext, i32);
impl_float!(f64, f64, F64Ext, i64);
impl_float!(F32, f32, F32Ext, i32);
impl_float!(F64, f64, F64Ext, i64);
impl_float!(f32, f32, i32);
impl_float!(f64, f64, i64);
impl_float!(F32, f32, i32);
impl_float!(F64, f64, i64);

#[cfg(not(feature = "std"))]
mod libm_adapters {
pub mod f32 {
pub fn abs(v: f32) -> f32 {
libm::fabsf(v)
}

pub fn floor(v: f32) -> f32 {
libm::floorf(v)
}

pub fn ceil(v: f32) -> f32 {
libm::ceilf(v)
}

pub fn trunc(v: f32) -> f32 {
libm::truncf(v)
}

pub fn round(v: f32) -> f32 {
libm::roundf(v)
}

pub fn fract(v: f32) -> f32 {
v - trunc(v)
}

pub fn sqrt(v: f32) -> f32 {
libm::sqrtf(v)
}
}

pub mod f64 {
pub fn abs(v: f64) -> f64 {
libm::fabs(v)
}

pub fn floor(v: f64) -> f64 {
libm::floor(v)
}

pub fn ceil(v: f64) -> f64 {
libm::ceil(v)
}

pub fn trunc(v: f64) -> f64 {
libm::trunc(v)
}

pub fn round(v: f64) -> f64 {
libm::round(v)
}

pub fn fract(v: f64) -> f64 {
v - trunc(v)
}

pub fn sqrt(v: f64) -> f64 {
libm::sqrt(v)
}
}
}
7 changes: 6 additions & 1 deletion test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
set -eux

EXTRA_ARGS=""
NO_STD_ARGS=""

if [ -n "${TARGET-}" ]; then
# Tests build in debug mode are prohibitively
Expand All @@ -12,8 +13,12 @@ if [ -n "${TARGET-}" ]; then
export RUSTFLAGS="--cfg debug_assertions"
fi

if [ -n "${TEST_NO_STD-}" ]; then
NO_STD_ARGS="--no-default-features --features=core"
fi

cd $(dirname $0)

time cargo test --all ${EXTRA_ARGS}
time cargo test --all ${EXTRA_ARGS} ${NO_STD_ARGS}

cd -