feat: no_std support

* no_std support

* Use integer log2 when possible to estimate allocations

* Remove std features from a few dependencies

* Use libm for missing funcs in nostd environment

* Use autocfg to detect presence of i128

* Better CI test for nostd environment

* Move benchmark to a separate crate

* Make the tests pass in no_std builds

* Use thumbv7m target for nostd build test

* Add documentation about the no-std compatibility

* Make zeroize and prime features no_std-compatible

* Test each feature in nostd context

* Fix mac test to work in no_std environment

* Avoid using thread_rng in nostd tests

* Fix prime tests in nostd mode

* Fix all warnings and errors

* Only test nostd builds on nightly on travis

* Travis: Only do nostd builds on nightly
This commit is contained in:
Robin Lambertz 2019-10-13 15:14:14 +02:00 committed by Friedel Ziegelmayer
parent 688ad829e5
commit 874ab0fbaa
34 changed files with 240 additions and 121 deletions

View File

@ -16,11 +16,6 @@ autobenches = false
[package.metadata.docs.rs]
features = ["std", "serde", "rand", "prime"]
[[bench]]
harness = false
name = "bench_main"
required-features = ["prime"]
[dependencies]
[dependencies.smallvec]
@ -43,37 +38,47 @@ default-features = false
optional = true
version = "0.6"
default-features = false
features = ["std"]
[dependencies.zeroize]
version = "0.6"
optional = true
default-features = false
features = ["zeroize_derive"]
[dependencies.serde]
optional = true
version = "1.0"
default-features = false
features = ["std"]
[dependencies.libm]
version = "0.1.4"
[dependencies.lazy_static]
version = "1.2.0"
default-features = false
# no_std feature is an anti-pattern. Why, lazy_static, why?
# See https://github.com/rust-lang-nursery/lazy-static.rs/issues/150
features = ["spin_no_std"]
[dependencies.byteorder]
version = "1.2.7"
default-features = false
[dev-dependencies]
criterion = "0.2"
rand_chacha = "0.1"
rand_xorshift = "0.1"
rand_isaac = "0.1"
[build-dependencies]
autocfg = "0.1.5"
[dev-dependencies.serde_test]
version = "1.0"
[features]
default = ["std", "i128", "u64_digit"]
i128 = ["num-integer/i128", "num-traits/i128"]
std = ["num-integer/std", "num-traits/std", "smallvec/std", "rand/std"]
std = ["num-integer/std", "num-traits/std", "smallvec/std", "rand/std", "serde/std", "zeroize/std"]
u64_digit = []
prime = ["rand"]
nightly = ["zeroize/nightly", "rand/nightly"]

View File

@ -26,10 +26,14 @@ extern crate num_bigint_dig as num_bigint;
## Features
The `std` crate feature is mandatory and enabled by default. If you depend on
`num-bigint` with `default-features = false`, you must manually enable the
`std` feature yourself. In the future, we hope to support `#![no_std]` with
the `alloc` crate when `std` is not enabled.
The `std` feature is enabled by default and mandatory to compile on older rust
version.
On Rust 1.36 and later, it is possible to use this crate on no_std target. If
you wish to compile for a target that does not have an `std` crate, you should
use `num-bigint` with `default-features = false`. All other sub-features should
be compatible with no_std. Note that in this mode, `num-bigint` still relies on
the alloc crate, so make sure you define a `global_allocator`.
Implementations for `i128` and `u128` are only available with Rust 1.26 and
later. The build script automatically detects this, but you can make it

View File

@ -0,0 +1,19 @@
[package]
name = "num-bigint-dig-benchmark"
version = "0.0.0"
autobenches = false
publish = false
[[bench]]
harness = false
name = "bench_main"
[dependencies]
num-bigint-dig = { path = "../", features = ["prime", "rand"] }
num-integer = "0.1.39"
num-traits = "0.2.4"
rand = "0.6"
rand_chacha = "0.1"
[dev-dependencies]
criterion = "0.2"

View File

@ -1,5 +1,3 @@
#![cfg(feature = "rand")]
use criterion::Criterion;
use num_bigint::{BigUint, RandBigInt};
use num_traits::Pow;

View File

View File

@ -1,35 +1,12 @@
extern crate autocfg;
use std::env;
use std::io::Write;
use std::process::{Command, Stdio};
fn main() {
if probe("fn main() { 0i128; }") {
let ac = autocfg::new();
if ac.probe_type("i128") {
println!("cargo:rustc-cfg=has_i128");
} else if env::var_os("CARGO_FEATURE_I128").is_some() {
panic!("i128 support was not detected!");
}
}
/// Test if a code snippet can be compiled
fn probe(code: &str) -> bool {
let rustc = env::var_os("RUSTC").unwrap_or_else(|| "rustc".into());
let out_dir = env::var_os("OUT_DIR").expect("environment variable OUT_DIR");
let mut child = Command::new(rustc)
.arg("--out-dir")
.arg(out_dir)
.arg("--emit=obj")
.arg("-")
.stdin(Stdio::piped())
.spawn()
.expect("rustc probe");
child
.stdin
.as_mut()
.expect("rustc stdin")
.write_all(code.as_bytes())
.expect("write rustc stdin");
child.wait().expect("rustc probe").success()
}
}

View File

@ -16,10 +16,27 @@ cargo test --verbose
cargo build --no-default-features --features="std"
cargo test --no-default-features --features="std"
# It should build in no_std
if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
rustup target add thumbv7m-none-eabi
cargo build --no-default-features --target=thumbv7m-none-eabi
# It should work in no_std on nightly.
# Note: Doctest might show an error: https://github.com/rust-lang/rust/issues/54010
# The "error" is wrong however, the doctests still run.
cargo test --no-default-features
fi
# Each isolated feature should also work everywhere.
for feature in $FEATURES; do
cargo build --verbose --no-default-features --features="std $feature"
cargo test --verbose --no-default-features --features="std $feature"
# Ensure that feature also works in nostd context on nightly.
if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
cargo build --verbose --no-default-features --features="$feature"
cargo test --verbose --no-default-features --features="$feature"
fi
done
# test all supported features together
@ -28,5 +45,6 @@ cargo test --features="std $FEATURES"
# make sure benchmarks can be built
if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
cd benchmark_crate
cargo bench --all-features --no-run
fi

View File

@ -1,4 +1,4 @@
use std::mem;
use core::mem;
/// Find last set bit
/// fls(0) == 0, fls(u32::MAX) == 32
@ -9,3 +9,12 @@ pub fn fls<T: num_traits::PrimInt>(v: T) -> usize {
pub fn ilog2<T: num_traits::PrimInt>(v: T) -> usize {
fls(v) - 1
}
/// Divide two integers, and ceil the result.
pub fn idiv_ceil<T: num_traits::PrimInt>(a: T, b: T) -> T {
if a % b != T::zero() {
a / b + T::one()
} else {
a / b
}
}

View File

@ -1,4 +1,4 @@
use std::cmp::Ordering::{self, Equal, Greater, Less};
use core::cmp::Ordering::{self, Equal, Greater, Less};
use crate::big_digit::BigDigit;

View File

@ -1,6 +1,6 @@
use num_traits::{One, Zero};
use smallvec::SmallVec;
use std::cmp::Ordering;
use core::cmp::Ordering;
use crate::algorithms::{add2, cmp_slice, sub2};
use crate::big_digit::{self, BigDigit, DoubleBigDigit};

View File

@ -4,8 +4,8 @@ use crate::bigint::{BigInt, ToBigInt};
use crate::biguint::{BigUint, IntDigits};
use integer::Integer;
use num_traits::{One, Signed, Zero};
use std::borrow::Cow;
use std::ops::Neg;
use alloc::borrow::Cow;
use core::ops::Neg;
/// XGCD sets z to the greatest common divisor of a and b and returns z.
/// If extended is true, XGCD returns their value such that z = a*x + b*y.
@ -98,8 +98,8 @@ fn lehmer_gcd(
// Ensure that a >= b
if a < b {
std::mem::swap(&mut a, &mut b);
std::mem::swap(&mut ua, &mut ub);
core::mem::swap(&mut a, &mut b);
core::mem::swap(&mut ua, &mut ub);
}
// loop invariant A >= B
@ -277,8 +277,8 @@ pub fn extended_gcd(
// Ensure that a >= b
if a < b {
std::mem::swap(&mut a, &mut b);
std::mem::swap(&mut ua, &mut ub);
core::mem::swap(&mut a, &mut b);
core::mem::swap(&mut ua, &mut ub);
}
let mut q: BigInt = 0.into();
@ -527,8 +527,8 @@ fn euclid_udpate(
*q = q_new;
*r = r_new;
std::mem::swap(a, b);
std::mem::swap(b, r);
core::mem::swap(a, b);
core::mem::swap(b, r);
if extended {
// ua, ub = ub, ua - q * ub
@ -546,7 +546,7 @@ fn euclid_udpate(
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
use core::str::FromStr;
use num_traits::FromPrimitive;
@ -574,11 +574,11 @@ mod tests {
while !r.is_zero() {
let quotient = &old_r / &r;
old_r = old_r - &quotient * &r;
std::mem::swap(&mut old_r, &mut r);
core::mem::swap(&mut old_r, &mut r);
old_s = old_s - &quotient * &s;
std::mem::swap(&mut old_s, &mut s);
core::mem::swap(&mut old_s, &mut s);
old_t = old_t - quotient * &t;
std::mem::swap(&mut old_t, &mut t);
core::mem::swap(&mut old_t, &mut t);
}
(old_r, old_s, old_t)

View File

@ -1,5 +1,5 @@
use std::cmp;
use std::iter::repeat;
use core::cmp;
use core::iter::repeat;
use crate::algorithms::{adc, add2, sub2, sub_sign};
use crate::big_digit::{BigDigit, DoubleBigDigit, BITS};
@ -310,7 +310,7 @@ mod tests {
#[cfg(feature = "u64_digit")]
#[test]
fn test_mac3_regression() {
let b: Vec<BigDigit> = vec![
let b = [
6871754923702299421,
18286959765922425554,
16443042141374662930,
@ -657,7 +657,7 @@ mod tests {
17511662813348858473,
12,
];
let c: Vec<BigDigit> = vec![
let c = [
13147625290258353449,
13817956093586917764,
18028234882233861888,
@ -1655,18 +1655,18 @@ mod tests {
1305238720762,
];
let mut a1: Vec<BigDigit> = vec![0; 1341];
let mut a2: Vec<BigDigit> = vec![0; 1341];
let mut a3: Vec<BigDigit> = vec![0; 1341];
let mut a1 = &mut [0; 1341];
let mut a2 = &mut [0; 1341];
let mut a3 = &mut [0; 1341];
//print!("{} {}", b.len(), c.len());
long(&mut a1, &b, &c);
karatsuba(&mut a2, &b, &c);
long(a1, &b, &c);
karatsuba(a2, &b, &c);
assert_eq!(a1, a2);
assert_eq!(&a1[..], &a2[..]);
// println!("res: {:?}", &a1);
toom3(&mut a3, &b, &c);
assert_eq!(a1, a3);
toom3(a3, &b, &c);
assert_eq!(&a1[..], &a3[..]);
}
}

View File

@ -1,4 +1,4 @@
use std::borrow::Cow;
use alloc::borrow::Cow;
use num_traits::{One, Signed};

View File

@ -1,5 +1,5 @@
use std::borrow::Cow;
use std::iter::repeat;
use alloc::borrow::Cow;
use core::iter::repeat;
use smallvec::SmallVec;

View File

@ -1,4 +1,4 @@
use std::borrow::Cow;
use alloc::borrow::Cow;
use num_traits::Zero;
use smallvec::SmallVec;

View File

@ -1,5 +1,5 @@
use std::cmp;
use std::cmp::Ordering::*;
use core::cmp;
use core::cmp::Ordering::*;
use num_traits::Zero;
use smallvec::SmallVec;

View File

@ -1,19 +1,21 @@
#![allow(clippy::suspicious_arithmetic_impl)]
#[allow(deprecated, unused_imports)]
use std::borrow::Cow;
use std::cmp::Ordering::{self, Equal, Greater, Less};
use std::default::Default;
use std::hash::{Hash, Hasher};
use std::iter::{Product, Sum};
use std::ops::{
use alloc::borrow::Cow;
use alloc::vec::Vec;
use alloc::string::String;
use core::cmp::Ordering::{self, Equal, Greater, Less};
use core::default::Default;
use core::hash::{Hash, Hasher};
use core::iter::{Product, Sum};
use core::ops::{
Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Div, DivAssign,
Mul, MulAssign, Neg, Not, Rem, RemAssign, Shl, ShlAssign, Shr, ShrAssign, Sub, SubAssign,
};
use std::str::{self, FromStr};
use std::{fmt, mem};
use core::str::{self, FromStr};
use core::{fmt, mem};
#[cfg(has_i128)]
use std::{i128, u128};
use std::{i64, u64};
use core::{i128, u128};
use core::{i64, u64};
#[cfg(feature = "serde")]
use serde;

View File

@ -270,7 +270,8 @@ impl Distribution<BigInt> for RandomBits {
/// to provide actually random primes.
///
/// # Example
/// ```
#[cfg_attr(feature = "std", doc = " ```")]
#[cfg_attr(not(feature = "std"), doc = " ```ignore")]
/// extern crate rand;
/// extern crate num_bigint_dig as num_bigint;
///

View File

@ -1,18 +1,22 @@
#[allow(deprecated, unused_imports)]
use std::ascii::AsciiExt;
use std::borrow::Cow;
use std::cmp::Ordering::{self, Equal, Greater, Less};
use std::default::Default;
use std::hash::{Hash, Hasher};
use std::iter::{Product, Sum};
use std::ops::{
use alloc::borrow::Cow;
use alloc::vec::Vec;
use alloc::string::String;
use core::cmp::Ordering::{self, Equal, Greater, Less};
use core::default::Default;
use core::hash::{Hash, Hasher};
use core::iter::{Product, Sum};
use core::ops::{
Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Div, DivAssign,
Mul, MulAssign, Neg, Rem, RemAssign, Shl, ShlAssign, Shr, ShrAssign, Sub, SubAssign,
};
use std::str::{self, FromStr};
use std::{cmp, fmt, mem};
use std::{f32, f64};
use std::{u64, u8};
use core::str::{self, FromStr};
use core::{cmp, fmt, mem};
use core::{f32, f64};
use core::{u64, u32, u8};
#[cfg(not(feature = "std"))]
use libm::F64Ext;
#[cfg(feature = "serde")]
use serde;
@ -22,9 +26,11 @@ use zeroize::Zeroize;
use integer::{Integer, Roots};
use num_traits::{
CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Float, FromPrimitive, Num, One, Pow,
CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, FromPrimitive, Num, One, Pow,
ToPrimitive, Unsigned, Zero,
};
use num_traits::float::FloatCore;
use BigInt;
use big_digit::{self, BigDigit};
@ -38,7 +44,7 @@ use self::monty::monty_modpow;
use super::VEC_SIZE;
use crate::algorithms::{__add2, __sub2rev, add2, sub2, sub2rev};
use crate::algorithms::{biguint_shl, biguint_shr};
use crate::algorithms::{cmp_slice, fls, ilog2};
use crate::algorithms::{cmp_slice, fls, ilog2, idiv_ceil};
use crate::algorithms::{div_rem, div_rem_digit, mac_with_carry, mul3, scalar_mul};
use crate::algorithms::{extended_gcd, mod_inverse};
use crate::traits::{ExtendedGcd, ModInverse};
@ -194,9 +200,9 @@ fn from_radix_digits_be(v: &[u8], radix: u32) -> BigUint {
debug_assert!(v.iter().all(|&c| (c as u32) < radix));
// Estimate how big the result will be, so we can pre-allocate it.
let bits = (radix as f64).log2() * v.len() as f64;
let big_digits = (bits / big_digit::BITS as f64).ceil();
let mut data = SmallVec::with_capacity(big_digits as usize);
let bits = ilog2(radix) * v.len();
let big_digits = idiv_ceil(bits, big_digit::BITS);
let mut data = SmallVec::with_capacity(big_digits);
let (base, power) = get_radix_base(radix);
let radix = radix as BigDigit;
@ -1713,14 +1719,14 @@ impl FromPrimitive for BigUint {
}
// match the rounding of casting from float to int
n = n.trunc();
n = FloatCore::trunc(n);
// handle 0.x, -0.x
if n.is_zero() {
return Some(BigUint::zero());
}
let (mantissa, exponent, sign) = Float::integer_decode(n);
let (mantissa, exponent, sign) = FloatCore::integer_decode(n);
if sign == -1 {
return None;
@ -1925,7 +1931,8 @@ fn to_radix_digits_le(u: &BigUint, radix: u32) -> Vec<u8> {
debug_assert!(!u.is_zero() && !radix.is_power_of_two());
// Estimate how big the result will be, so we can pre-allocate it.
let radix_digits = ((u.bits() as f64) / (radix as f64).log2()).ceil();
let bits = ilog2(radix);
let radix_digits = idiv_ceil(u.bits(), bits);
let mut res = Vec::with_capacity(radix_digits as usize);
let mut digits = u.clone();
@ -2518,7 +2525,7 @@ impl serde::Serialize for BigUint {
.iter()
.enumerate()
.flat_map(|(i, n)| {
if i == last && n < &(::std::u32::MAX as u64) {
if i == last && n < &(u32::MAX as u64) {
vec![*n as u32]
} else {
vec![*n as u32, (n >> 32) as u32]

View File

@ -50,7 +50,9 @@
//!
//! It's easy to generate large random numbers:
//!
//! ```rust
#![cfg_attr(feature = "std", doc = " ```")]
#![cfg_attr(not(feature = "std"), doc = " ```ignore")]
//!
//! # #[cfg(feature = "rand")]
//! extern crate rand;
//! extern crate num_bigint_dig as bigint;
@ -78,12 +80,37 @@
//! ## Compatibility
//!
//! The `num-bigint` crate is tested for rustc 1.15 and greater.
//!
//! ## `no_std` compatibility
//!
//! This crate is compatible with `no_std` environments from Rust 1.36. Note
//! however that it still requires the `alloc` crate, so the user should ensure
//! that they set a `global_allocator`.
//!
//! To use in no_std environment, add the crate as such in your `Cargo.toml`
//! file:
//!
//! ```toml
//! [dependencies]
//! num-bigint = { version = "0.3", default-features=false }
//! ```
//!
//! Every features should be compatible with no_std environment, so feel free to
//! add features like `prime`, `i128`, etc...
#![doc(html_root_url = "https://docs.rs/num-bigint/0.2")]
// We don't actually support `no_std` yet, and probably won't until `alloc` is stable. We're just
// reserving this ability with the "std" feature now, and compilation will fail without.
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(feature = "std"))]
#[macro_use]
extern crate alloc;
#[cfg(feature = "std")]
use std as alloc;
#[cfg(feature = "std")]
extern crate core;
#[cfg(feature = "rand")]
extern crate rand;
#[cfg(all(test, feature = "rand"))]
@ -113,8 +140,11 @@ extern crate num_traits;
#[cfg(feature = "prime")]
extern crate byteorder;
extern crate libm;
#[cfg(feature = "std")]
use std::error::Error;
use std::fmt;
use core::fmt;
#[macro_use]
mod macros;
@ -182,6 +212,7 @@ impl fmt::Display for ParseBigIntError {
}
}
#[cfg(feature = "std")]
impl Error for ParseBigIntError {
fn description(&self) -> &str {
self.__description()

View File

@ -1,7 +1,8 @@
#![allow(clippy::many_single_char_names)]
use num_traits::{One, Zero};
use std::ops::Shl;
use core::ops::Shl;
use alloc::vec::Vec;
use big_digit::{self, BigDigit, DoubleBigDigit, SignedDoubleBigDigit};
use biguint::BigUint;
@ -193,7 +194,7 @@ pub fn monty_modpow(x: &BigUint, y: &BigUint, m: &BigUint) -> BigUint {
mr.n0inv,
num_words,
);
::std::mem::swap(&mut z, &mut zz);
core::mem::swap(&mut z, &mut zz);
yi <<= n;
j += n;
}

View File

@ -388,7 +388,7 @@ pub fn probably_prime_lucas(n: &BigUint) -> bool {
let mut t2 = &vk1 << 1;
if t1 < t2 {
::std::mem::swap(&mut t1, &mut t2);
core::mem::swap(&mut t1, &mut t2);
}
t1 -= t2;
@ -440,6 +440,7 @@ fn get_bit(x: &BigUint, i: usize) -> u8 {
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec::Vec;
// use RandBigInt;
use crate::biguint::ToBigUint;

View File

@ -20,7 +20,8 @@ use std::{i16, i32, i64, i8, isize};
use std::{u16, u32, u64, u8, usize};
use num_integer::Integer;
use num_traits::{Float, FromPrimitive, Num, One, Pow, Signed, ToPrimitive, Zero};
use num_traits::{FromPrimitive, Num, One, Pow, Signed, ToPrimitive, Zero};
use num_traits::float::FloatCore;
mod consts;
use consts::*;
@ -1119,9 +1120,22 @@ fn test_negative_shr() {
#[test]
#[cfg(feature = "rand")]
fn test_random_shr() {
use rand::distributions::Standard;
#[cfg(feature = "std")]
fn thread_rng() -> impl rand::Rng {
rand::thread_rng()
}
#[cfg(not(feature = "std"))]
fn thread_rng() -> impl rand::Rng {
use rand::SeedableRng;
// Chosen by fair dice roll
rand::rngs::StdRng::seed_from_u64(4)
}
use rand::Rng;
let mut rng = rand::thread_rng();
use rand::distributions::Standard;
let mut rng = thread_rng();
for p in rng.sample_iter::<i64, _>(&Standard).take(1000) {
let big = BigInt::from(p);

View File

@ -20,10 +20,12 @@ use std::{i128, u128};
use std::{u16, u32, u64, u8, usize};
use num_traits::{
CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Float, FromPrimitive, Num, One, Pow,
CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, FromPrimitive, Num, One, Pow,
ToPrimitive, Zero,
};
use num_traits::float::FloatCore;
mod consts;
use consts::*;

View File

@ -11,9 +11,18 @@ mod biguint {
use num_bigint::{BigUint, RandBigInt, RandomBits};
use num_traits::Zero;
use rand::distributions::Uniform;
use rand::thread_rng;
use rand::{Rng, SeedableRng};
#[cfg(feature = "std")]
fn thread_rng() -> impl Rng {
rand::thread_rng()
}
#[cfg(not(feature = "std"))]
fn thread_rng() -> impl Rng {
// Chosen by fair dice roll
rand::StdRng::seed_from_u64(4)
}
#[test]
fn test_rand() {
let mut rng = thread_rng();
@ -216,9 +225,18 @@ mod bigint {
use num_bigint::{BigInt, RandBigInt, RandomBits};
use num_traits::Zero;
use rand::distributions::Uniform;
use rand::thread_rng;
use rand::{Rng, SeedableRng};
#[cfg(feature = "std")]
fn thread_rng() -> impl Rng {
rand::thread_rng()
}
#[cfg(not(feature = "std"))]
fn thread_rng() -> impl Rng {
// Chosen by fair dice roll
rand::rngs::StdRng::seed_from_u64(4)
}
#[test]
fn test_rand() {
let mut rng = thread_rng();

View File

@ -104,9 +104,21 @@ mod biguint {
#[cfg(feature = "rand")]
#[test]
fn test_roots_rand() {
#[cfg(feature = "std")]
fn thread_rng() -> impl rand::Rng {
rand::thread_rng()
}
#[cfg(not(feature = "std"))]
fn thread_rng() -> impl rand::Rng {
use rand::SeedableRng;
// Chosen by fair dice roll
rand::rngs::StdRng::seed_from_u64(4)
}
use num_bigint::RandBigInt;
use rand::Rng;
use rand::distributions::Uniform;
use rand::{thread_rng, Rng};
let mut rng = thread_rng();
let bit_range = Uniform::new(0, 2048);