Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
8 changes: 7 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#![allow(unused_features)]
#![no_std]
#![feature(asm)]
#![feature(core_intrinsics)]
#![feature(naked_functions)]
#![cfg_attr(not(test), no_std)]
// TODO(rust-lang/rust#35021) uncomment when that PR lands
// #![feature(rustc_builtins)]

Expand All @@ -13,13 +13,19 @@
#[macro_use]
extern crate quickcheck;

#[cfg(test)]
extern crate core;

#[cfg(target_arch = "arm")]
pub mod arm;

pub mod udiv;
pub mod mul;
pub mod shift;

#[cfg(test)]
mod qc;

/// Trait for some basic operations on integers
trait Int {
fn bits() -> u32;
Expand Down
11 changes: 8 additions & 3 deletions src/mul.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,17 @@ mulo!(__mulodi4: i64);

#[cfg(test)]
mod tests {
use qc::{I32, I64, U64};

quickcheck! {
fn muldi(a: u64, b: u64) -> bool {
fn muldi(a: U64, b: U64) -> bool {
let (a, b) = (a.0, b.0);
let r = super::__muldi4(a, b);
r == a.wrapping_mul(b)
}

fn mulosi(a: i32, b: i32) -> bool {
fn mulosi(a: I32, b: I32) -> bool {
let (a, b) = (a.0, b.0);
let mut overflow = 2;
let r = super::__mulosi4(a, b, &mut overflow);
if overflow != 0 && overflow != 1 {
Expand All @@ -87,7 +91,8 @@ mod tests {
(r, overflow != 0) == a.overflowing_mul(b)
}

fn mulodi(a: i64, b: i64) -> bool {
fn mulodi(a: I64, b: I64) -> bool {
let (a, b) = (a.0, b.0);
let mut overflow = 2;
let r = super::__mulodi4(a, b, &mut overflow);
if overflow != 0 && overflow != 1 {
Expand Down
123 changes: 123 additions & 0 deletions src/qc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// When testing functions, QuickCheck (QC) uses small values for integer (`u*`/`i*`) arguments
// (~ `[-100, 100]`), but these values don't stress all the code paths in our intrinsics. Here we
// create newtypes over the primitive integer types with the goal of having full control over the
// random values that will be used to test our intrinsics.

use std::boxed::Box;
use std::fmt;

use quickcheck::{Arbitrary, Gen};

use LargeInt;

// Generates values in the full range of the integer type
macro_rules! arbitrary {
($TY:ident : $ty:ident) => {
#[derive(Clone, Copy)]
pub struct $TY(pub $ty);

impl Arbitrary for $TY {
fn arbitrary<G>(g: &mut G) -> $TY
where G: Gen
{
$TY(g.gen())
}

fn shrink(&self) -> Box<Iterator<Item=$TY>> {
struct Shrinker {
x: $ty,
}

impl Iterator for Shrinker {
type Item = $TY;

fn next(&mut self) -> Option<$TY> {
self.x /= 2;
if self.x == 0 {
None
} else {
Some($TY(self.x))
}
}
}

if self.0 == 0 {
::quickcheck::empty_shrinker()
} else {
Box::new(Shrinker { x: self.0 })
}
}
}

impl fmt::Debug for $TY {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
}
}

arbitrary!(I32: i32);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure if these should also use the arbitrary_large generation method. Does any intrinsic have special handling of cases where the lower/higher half-word is zero?

arbitrary!(U32: u32);

// These integers are "too large". If we generate e.g. `u64` values in the full range then there's
// only `1 / 2^32` chance of seeing a value smaller than `2^32` (i.e. whose higher "word" (32-bits)
// is `0`)! But this is an important group of values to tests because we have special code paths for
// them. Instead we'll generate e.g. `u64` integers this way: uniformly pick between (a) setting the
// low word to 0 and generating a random high word, (b) vice versa: high word to 0 and random low
// word or (c) generate both words randomly. This let's cover better the code paths in our
// intrinsics.
macro_rules! arbitrary_large {
($TY:ident : $ty:ident) => {
#[derive(Clone, Copy)]
pub struct $TY(pub $ty);

impl Arbitrary for $TY {
fn arbitrary<G>(g: &mut G) -> $TY
where G: Gen
{
if g.gen() {
$TY($ty::from_parts(g.gen(), g.gen()))
} else if g.gen() {
$TY($ty::from_parts(0, g.gen()))
} else {
$TY($ty::from_parts(g.gen(), 0))
}
}

fn shrink(&self) -> Box<Iterator<Item=$TY>> {
struct Shrinker {
x: $ty,
}

impl Iterator for Shrinker {
type Item = $TY;

fn next(&mut self) -> Option<$TY> {
self.x /= 2;
if self.x == 0 {
None
} else {
Some($TY(self.x))
}
}
}

if self.0 == 0 {
::quickcheck::empty_shrinker()
} else {
Box::new(Shrinker { x: self.0 })
}
}
}

impl fmt::Debug for $TY {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
}
}

arbitrary_large!(I64: i64);
arbitrary_large!(U64: u64);
11 changes: 8 additions & 3 deletions src/shift.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,12 @@ lshr!(__lshrdi3: u64);
#[cfg(test)]
mod tests {
use quickcheck::TestResult;
use qc::{I64, U64};

// NOTE We purposefully stick to `u32` for `b` here because we want "small" values (b < 64)
quickcheck! {
fn ashldi(a: u64, b: u32) -> TestResult {
fn ashldi(a: U64, b: u32) -> TestResult {
let a = a.0;
if b >= 64 {
TestResult::discard()
} else {
Expand All @@ -72,7 +75,8 @@ mod tests {
}
}

fn ashrdi(a: i64, b: u32) -> TestResult {
fn ashrdi(a: I64, b: u32) -> TestResult {
let a = a.0;
if b >= 64 {
TestResult::discard()
} else {
Expand All @@ -81,7 +85,8 @@ mod tests {
}
}

fn lshrdi(a: u64, b: u32) -> TestResult {
fn lshrdi(a: U64, b: u32) -> TestResult {
let a = a.0;
if b >= 64 {
TestResult::discard()
} else {
Expand Down
19 changes: 13 additions & 6 deletions src/udiv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,9 +229,11 @@ pub extern "C" fn __udivmoddi4(n: u64, d: u64, rem: Option<&mut u64>) -> u64 {
#[cfg(test)]
mod tests {
use quickcheck::TestResult;
use qc::{U32, U64};

quickcheck!{
fn udivdi3(n: u64, d: u64) -> TestResult {
fn udivdi3(n: U64, d: U64) -> TestResult {
let (n, d) = (n.0, d.0);
if d == 0 {
TestResult::discard()
} else {
Expand All @@ -240,7 +242,8 @@ mod tests {
}
}

fn umoddi3(n: u64, d: u64) -> TestResult {
fn umoddi3(n: U64, d: U64) -> TestResult {
let (n, d) = (n.0, d.0);
if d == 0 {
TestResult::discard()
} else {
Expand All @@ -249,7 +252,8 @@ mod tests {
}
}

fn udivmoddi4(n: u64, d: u64) -> TestResult {
fn udivmoddi4(n: U64, d: U64) -> TestResult {
let (n, d) = (n.0, d.0);
if d == 0 {
TestResult::discard()
} else {
Expand All @@ -259,7 +263,8 @@ mod tests {
}
}

fn udivsi3(n: u32, d: u32) -> TestResult {
fn udivsi3(n: U32, d: U32) -> TestResult {
let (n, d) = (n.0, d.0);
if d == 0 {
TestResult::discard()
} else {
Expand All @@ -268,7 +273,8 @@ mod tests {
}
}

fn umodsi3(n: u32, d: u32) -> TestResult {
fn umodsi3(n: U32, d: U32) -> TestResult {
let (n, d) = (n.0, d.0);
if d == 0 {
TestResult::discard()
} else {
Expand All @@ -277,7 +283,8 @@ mod tests {
}
}

fn udivmodsi4(n: u32, d: u32) -> TestResult {
fn udivmodsi4(n: U32, d: U32) -> TestResult {
let (n, d) = (n.0, d.0);
if d == 0 {
TestResult::discard()
} else {
Expand Down