Expand and refactor teting infrastructure

This commit moves over most of the testing infrastructure to in-tree docker
images that are all dispatched to from Travis (no other test configuration).
This allows versioning modifications to the test infrastructure as well as the
code itself. Additionally separate docker images allows for easy modification of
one without worrying about tampering of others as well as easy addition of new
targets by simply adding a new `Dockerfile`.

Additionally this commit bundles the master version of the `compiler-rt` source
repository from `llvm-mirror/compiler-rt` to test against. The compiler-rt
library itself is compiled as a `cdylib` which is then dynamically located at
runtime and we look for symbols in. There's a few hoops here, but they currently
get the job done.

All tests now execute against both gcc_s and compiler-rt, and this
testing strategy is now all hidden behind a macro as well (refactoring
all existing tests along the way).
This commit is contained in:
Alex Crichton 2016-09-26 22:22:10 -07:00
parent c56a3f8a6e
commit 8e161a791a
38 changed files with 624 additions and 526 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "compiler-rt/compiler-rt-cdylib/compiler-rt"]
path = compiler-rt/compiler-rt-cdylib/compiler-rt
url = https://github.com/llvm-mirror/compiler-rt

View File

@ -1,62 +1,63 @@
dist: trusty
language: generic
language: rust
services: docker
sudo: required
rust: nightly
cache: cargo
matrix:
include:
- env: TARGET=aarch64-unknown-linux-gnu
os: linux
- env: TARGET=arm-unknown-linux-gnueabi
os: linux
# FIXME(rust-lang/rust#36518)
rust: nightly-2016-09-21
- env: TARGET=arm-unknown-linux-gnueabihf
os: linux
- env: TARGET=armv7-unknown-linux-gnueabihf
os: linux
- env: TARGET=i586-unknown-linux-gnu
os: linux
- env: TARGET=i686-apple-darwin
language: ruby
# FIXME(rust-lang/rust#36793)
rust: nightly-2016-09-10
os: osx
- env: TARGET=i686-unknown-linux-gnu
os: linux
# FIXME(rust-lang/rust#36793)
rust: nightly-2016-09-10
- env: TARGET=mips-unknown-linux-gnu
os: linux
- env: TARGET=mipsel-unknown-linux-gnu
os: linux
- env: TARGET=powerpc-unknown-linux-gnu
os: linux
- env: TARGET=powerpc64-unknown-linux-gnu
os: linux
- env: TARGET=powerpc64le-unknown-linux-gnu
os: linux
# QEMU crashes even when executing the simplest cross compiled C program:
# `int main() { return 0; }`
- env: TARGET=powerpc64le-unknown-linux-gnu NO_RUN=1
- env: TARGET=thumbv6m-none-eabi
os: linux
- env: TARGET=thumbv6m-none-eabi WEAK=true
os: linux
install: cargo install xargo --debug -f
script: $HOME/.cargo/bin/xargo build --target $TARGET
- env: TARGET=thumbv7em-none-eabi
os: linux
- env: TARGET=thumbv7em-none-eabi WEAK=true
os: linux
install: cargo install xargo --debug -f
script: $HOME/.cargo/bin/xargo build --target $TARGET
- env: TARGET=thumbv7em-none-eabihf
os: linux
- env: TARGET=thumbv7em-none-eabihf WEAK=true
os: linux
- env: TARGET=thumbv7m-none-eabi
os: linux
- env: TARGET=thumbv7m-none-eabi WEAK=true
os: linux
install: cargo install xargo --debug -f
script: $HOME/.cargo/bin/xargo build --target $TARGET
- env: TARGET=x86_64-apple-darwin
language: ruby
os: osx
- env: TARGET=x86_64-unknown-linux-gnu
os: linux
env: TARGET=x86_64-unknown-linux-gnu
before_install:
- test "$TRAVIS_OS_NAME" = "osx" || docker run --rm --privileged multiarch/qemu-user-static:register
install:
- bash ci/install.sh
- curl https://static.rust-lang.org/rustup.sh |
sh -s -- --add-target=$TARGET --disable-sudo -y --prefix=`rustc --print sysroot`
script:
- bash ci/script.sh
- cargo generate-lockfile
- if [[ $TRAVIS_OS_NAME = "linux" ]]; then
sudo apt-get remove -y qemu-user-static &&
sudo apt-get install -y qemu-user-static &&
sh ci/run-docker.sh $TARGET;
else
cargo test --target $TARGET &&
cargo test --target $TARGET --release;
fi
branches:
only:

View File

@ -13,9 +13,8 @@ optional = true
[dev-dependencies]
quickcheck = "0.3.1"
rand = "0.3.14"
[dev-dependencies.gcc_s]
path = "gcc_s"
gcc_s = { path = "gcc_s" }
compiler-rt = { path = "compiler-rt" }
[features]
weak = ["rlibc/weak"]

View File

@ -16,8 +16,8 @@ See [rust-lang/rust#35437][0].
If you are working with a target that doesn't have binary releases of std available via rustup (this
probably means you are building the core crate yourself) and need compiler-rt intrinsics (i.e. you
are probably getting linker errors when building an executable: "undefined reference to
__aeabi_memcpy"), you can use this crate to get those intrinsics and solve the linker errors. To do
are probably getting linker errors when building an executable: `undefined reference to
__aeabi_memcpy`), you can use this crate to get those intrinsics and solve the linker errors. To do
that, simply add this crate as a Cargo dependency (it doesn't matter where in the dependency graph
this crate ends up, as long as it's there):
@ -52,7 +52,7 @@ porting that particular intrinsic.
1. [Rust][4] and [C][5] have slightly different operator precedence. C evaluates comparisons (`== !=`) before bitwise operations (`& | ^`), while Rust evaluates the other way.
2. C assumes wrapping operations everywhere. Rust panics on overflow when in debug mode. Consider using the [Wrapping][6] type or the explicit [wrapping_*][7] functions where applicable.
3. Note [C implicit casts][8], especially integer promotion. Rust is much more explicit about casting, so be sure that any cast which affects the output is ported to the Rust implementation.
4. Rust has [many functions][9] for integer or floating point manipulation in the standard library. Consider using one of these functions rather than porting a new one.
4. Rust has [many functions][9] for integer or floating point manipulation in the standard library. Consider using one of these functions rather than porting a new one.
[4]: https://doc.rust-lang.org/reference.html#operator-precedence
[5]: http://en.cppreference.com/w/c/language/operator_precedence

View File

@ -4,6 +4,7 @@ environment:
- TARGET: x86_64-pc-windows-msvc
install:
- git submodule update --init
- curl -sSf -o rustup-init.exe https://win.rustup.rs
- rustup-init.exe --default-host %TARGET% --default-toolchain nightly -y
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin

View File

@ -4,4 +4,5 @@ fn main() {
if env::var("TARGET").unwrap().ends_with("gnueabihf") {
println!("cargo:rustc-cfg=gnueabihf")
}
println!("cargo:rerun-if-changed=build.rs");
}

View File

@ -0,0 +1,10 @@
FROM ubuntu:16.04
RUN apt-get update
RUN apt-get install -y --no-install-recommends \
gcc libc6-dev ca-certificates \
gcc-aarch64-linux-gnu libc6-dev-arm64-cross \
qemu-user-static
ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc \
PATH=$PATH:/rust/bin \
QEMU_LD_PREFIX=/usr/aarch64-linux-gnu \
RUST_TEST_THREADS=1

View File

@ -0,0 +1,10 @@
FROM ubuntu:16.04
RUN apt-get update
RUN apt-get install -y --no-install-recommends \
gcc libc6-dev ca-certificates \
gcc-arm-linux-gnueabi libc6-dev-armel-cross qemu-user-static
ENV CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABI_LINKER=arm-linux-gnueabi-gcc \
PATH=$PATH:/rust/bin \
QEMU_LD_PREFIX=/usr/arm-linux-gnueabi \
RUST_TEST_THREADS=1

View File

@ -0,0 +1,9 @@
FROM ubuntu:16.04
RUN apt-get update
RUN apt-get install -y --no-install-recommends \
gcc libc6-dev ca-certificates \
gcc-arm-linux-gnueabihf libc6-dev-armhf-cross qemu-user-static
ENV CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc \
PATH=$PATH:/rust/bin \
QEMU_LD_PREFIX=/usr/arm-linux-gnueabihf \
RUST_TEST_THREADS=1

View File

@ -0,0 +1,9 @@
FROM ubuntu:16.04
RUN apt-get update
RUN apt-get install -y --no-install-recommends \
gcc libc6-dev ca-certificates \
gcc-arm-linux-gnueabihf libc6-dev-armhf-cross qemu-user-static
ENV CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc \
PATH=$PATH:/rust/bin \
QEMU_LD_PREFIX=/usr/arm-linux-gnueabihf \
RUST_TEST_THREADS=1

View File

@ -0,0 +1,5 @@
FROM ubuntu:16.04
RUN apt-get update
RUN apt-get install -y --no-install-recommends \
gcc-multilib libc6-dev ca-certificates
ENV PATH=$PATH:/rust/bin

View File

@ -0,0 +1,5 @@
FROM ubuntu:16.04
RUN apt-get update
RUN apt-get install -y --no-install-recommends \
gcc-multilib libc6-dev ca-certificates
ENV PATH=$PATH:/rust/bin

View File

@ -0,0 +1,12 @@
FROM ubuntu:16.04
RUN apt-get update
RUN apt-get install -y --no-install-recommends \
gcc libc6-dev ca-certificates \
gcc-mips-linux-gnu libc6-dev-mips-cross \
binfmt-support qemu-user-static qemu-system-mips
ENV CARGO_TARGET_MIPS_UNKNOWN_LINUX_GNU_LINKER=mips-linux-gnu-gcc \
PATH=$PATH:/rust/bin \
QEMU_LD_PREFIX=/usr/mips-linux-gnu \
RUST_TEST_THREADS=1

View File

@ -0,0 +1,12 @@
FROM ubuntu:16.04
RUN apt-get update
RUN apt-get install -y --no-install-recommends \
gcc libc6-dev ca-certificates \
gcc-mipsel-linux-gnu libc6-dev-mipsel-cross \
binfmt-support qemu-user-static
ENV CARGO_TARGET_MIPSEL_UNKNOWN_LINUX_GNU_LINKER=mipsel-linux-gnu-gcc \
PATH=$PATH:/rust/bin \
QEMU_LD_PREFIX=/usr/mipsel-linux-gnu \
RUST_TEST_THREADS=1

View File

@ -0,0 +1,12 @@
FROM ubuntu:16.04
RUN apt-get update
RUN apt-get install -y --no-install-recommends \
gcc libc6-dev qemu-user-static ca-certificates \
gcc-powerpc-linux-gnu libc6-dev-powerpc-cross \
qemu-system-ppc
ENV CARGO_TARGET_POWERPC_UNKNOWN_LINUX_GNU_LINKER=powerpc-linux-gnu-gcc \
PATH=$PATH:/rust/bin \
QEMU_LD_PREFIX=/usr/powerpc-linux-gnu \
RUST_TEST_THREADS=1

View File

@ -0,0 +1,13 @@
FROM ubuntu:16.04
RUN apt-get update
RUN apt-get install -y --no-install-recommends \
gcc libc6-dev ca-certificates \
gcc-powerpc64-linux-gnu libc6-dev-ppc64-cross \
binfmt-support qemu-user-static qemu-system-ppc
ENV CARGO_TARGET_POWERPC64_UNKNOWN_LINUX_GNU_LINKER=powerpc64-linux-gnu-gcc \
CC_powerpc64_unknown_linux_gnu=powerpc64-linux-gnu-gcc \
PATH=$PATH:/rust/bin \
QEMU_LD_PREFIX=/usr/powerpc64-linux-gnu \
RUST_TEST_THREADS=1

View File

@ -0,0 +1,13 @@
FROM ubuntu:16.04
RUN apt-get update
RUN apt-get install -y --no-install-recommends \
gcc libc6-dev qemu-user-static ca-certificates \
gcc-powerpc64le-linux-gnu libc6-dev-ppc64el-cross \
qemu-system-ppc
ENV CARGO_TARGET_POWERPC64LE_UNKNOWN_LINUX_GNU_LINKER=powerpc64le-linux-gnu-gcc \
CC_powerpc64le_unknown_linux_gnu=powerpc64le-linux-gnu-gcc \
PATH=$PATH:/rust/bin \
QEMU_LD_PREFIX=/usr/powerpc64le-linux-gnu \
RUST_TEST_THREADS=1

View File

@ -0,0 +1,6 @@
FROM ubuntu:16.04
RUN apt-get update
RUN apt-get install -y --no-install-recommends \
gcc libc6-dev ca-certificates
ENV PATH=$PATH:/rust/bin

View File

@ -1,51 +0,0 @@
case $TRAVIS_OS_NAME in
linux)
HOST=x86_64-unknown-linux-gnu
NM=nm
OBJDUMP=objdump
LINUX=y
;;
osx)
HOST=x86_64-apple-darwin
NM=gnm
OBJDUMP=gobjdump
OSX=y
;;
esac
# NOTE For rustup
export PATH="$HOME/.cargo/bin:$PATH"
CARGO=cargo
RUN_TESTS=y
# NOTE For the host and its 32-bit variants we don't need prefixed tools or QEMU
if [[ $TARGET != $HOST && ! $TARGET =~ ^i.86- ]]; then
GCC_TRIPLE=${TARGET//unknown-/}
case $TARGET in
armv7-unknown-linux-gnueabihf)
GCC_TRIPLE=arm-linux-gnueabihf
;;
powerpc64le-unknown-linux-gnu)
# QEMU crashes even when executing the simplest cross compiled C program:
# `int main() { return 0; }`
RUN_TESTS=n
;;
thumbv*-none-eabi*)
CARGO=xargo
GCC_TRIPLE=arm-none-eabi
# Bare metal targets. No `std` or `test` crates for these targets.
RUN_TESTS=n
;;
esac
export CARGO_TARGET_$(echo $TARGET | tr a-z- A-Z_)_LINKER=$GCC_TRIPLE-gcc
if [[ $RUN_TESTS == y ]]; then
# NOTE(export) so this can reach the processes that `cargo test` spawns
export QEMU_LD_PREFIX=/usr/$GCC_TRIPLE
fi
PREFIX=$GCC_TRIPLE-
fi

View File

@ -1,53 +0,0 @@
set -ex
. $(dirname $0)/env.sh
install_qemu() {
if [[ $QEMU_LD_PREFIX ]]; then
apt-get update
apt-get install -y --no-install-recommends \
binfmt-support qemu-user-static
fi
}
install_gist() {
if [[ $OSX ]]; then
gem install gist -v 4.5.0
fi
}
install_binutils() {
if [[ $OSX ]]; then
brew install binutils
fi
}
install_rust() {
if [[ $OSX ]]; then
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain=nightly
else
rustup default nightly
fi
rustup -V
rustc -V
cargo -V
}
add_rustup_target() {
if [[ $TARGET != $HOST && $CARGO == cargo ]]; then
rustup target add $TARGET
fi
}
main() {
if [[ $OSX || ${IN_DOCKER_CONTAINER:-n} == y ]]; then
install_qemu
install_gist
install_binutils
install_rust
add_rustup_target
fi
}
main

29
ci/run-docker.sh Normal file
View File

@ -0,0 +1,29 @@
# Small script to run tests for a target (or all targets) inside all the
# respective docker images.
set -ex
run() {
echo $1
CMD="cargo test --target $1"
if [ "$NO_RUN" = "1" ]; then
CMD="$CMD --no-run"
fi
docker build -t libc ci/docker/$1
docker run \
-v `rustc --print sysroot`:/rust:ro \
-v `pwd`:/checkout:ro \
-e CARGO_TARGET_DIR=/tmp/target \
-w /checkout \
--privileged \
-it libc \
bash -c "$CMD && $CMD --release"
}
if [ -z "$1" ]; then
for d in `ls ci/docker/`; do
run $d
done
else
run $1
fi

View File

@ -1,80 +0,0 @@
set -ex
. $(dirname $0)/env.sh
gist_it() {
gist -d "'$TARGET/rustc-builtins.rlib' from commit '$TRAVIS_COMMIT' on branch '$TRAVIS_BRANCH'"
echo "Disassembly available at the above URL."
}
build() {
if [[ $WEAK ]]; then
$CARGO build --features weak --target $TARGET
$CARGO build --features weak --target $TARGET --release
else
$CARGO build --target $TARGET
$CARGO build --target $TARGET --release
fi
}
inspect() {
$PREFIX$NM -g --defined-only target/**/debug/*.rlib
set +e
$PREFIX$OBJDUMP -Cd target/**/release/*.rlib | gist_it
set -e
# Check presence/absence of weak symbols
if [[ $WEAK ]]; then
local symbols=( memcmp memcpy memmove memset )
for symbol in "${symbols[@]}"; do
$PREFIX$NM target/$TARGET/debug/deps/librlibc-*.rlib | grep -q "W $symbol"
done
else
set +e
ls target/$TARGET/debug/deps/librlibc-*.rlib
if [[ $? == 0 ]]; then
exit 1
fi
set -e
fi
}
run_tests() {
if [[ $QEMU_LD_PREFIX ]]; then
export RUST_TEST_THREADS=1
fi
if [[ $RUN_TESTS == y ]]; then
cargo test --target $TARGET
cargo test --target $TARGET --release
fi
}
main() {
if [[ $LINUX && ${IN_DOCKER_CONTAINER:-n} == n ]]; then
# NOTE The Dockerfile of this image is in the docker branch of this repository
docker run \
--privileged \
-e IN_DOCKER_CONTAINER=y \
-e TARGET=$TARGET \
-e TRAVIS_BRANCH=$TRAVIS_BRANCH \
-e TRAVIS_COMMIT=$TRAVIS_COMMIT \
-e TRAVIS_OS_NAME=$TRAVIS_OS_NAME \
-e WEAK=$WEAK \
-v $(pwd):/mnt \
japaric/rustc-builtins \
sh -c 'cd /mnt;
bash ci/install.sh;
bash ci/script.sh'
else
build
inspect
run_tests
fi
}
main

8
compiler-rt/Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "compiler-rt"
version = "0.1.0"
authors = ["Alex Crichton <alex@alexcrichton.com>"]
[dependencies]
compiler-rt-cdylib = { path = "compiler-rt-cdylib" }
libloading = "0.3"

View File

@ -0,0 +1,11 @@
[package]
name = "compiler-rt-cdylib"
version = "0.1.0"
authors = ["Alex Crichton <alex@alexcrichton.com>"]
build = "build.rs"
[lib]
crate-type = ["cdylib"]
[build-dependencies]
gcc = "0.3"

View File

@ -0,0 +1,68 @@
extern crate gcc;
use std::env;
use std::path::Path;
use std::process::Command;
struct Sources {
files: Vec<&'static str>,
}
impl Sources {
fn new() -> Sources {
Sources { files: Vec::new() }
}
fn extend(&mut self, sources: &[&'static str]) {
self.files.extend(sources);
}
}
fn main() {
if !Path::new("compiler-rt/.git").exists() {
let _ = Command::new("git").args(&["submodule", "update", "--init"])
.status();
}
let target = env::var("TARGET").expect("TARGET was not set");
let cfg = &mut gcc::Config::new();
if target.contains("msvc") {
cfg.define("__func__", Some("__FUNCTION__"));
} else {
cfg.flag("-fno-builtin");
cfg.flag("-fomit-frame-pointer");
cfg.flag("-ffreestanding");
}
let mut sources = Sources::new();
sources.extend(&[
"muldi3.c",
"mulosi4.c",
"mulodi4.c",
"divsi3.c",
"divdi3.c",
"modsi3.c",
"moddi3.c",
"divmodsi4.c",
"divmoddi4.c",
"ashldi3.c",
"ashrdi3.c",
"lshrdi3.c",
"udivdi3.c",
"umoddi3.c",
"udivmoddi4.c",
"udivsi3.c",
"umodsi3.c",
"udivmodsi4.c",
"adddf3.c",
"addsf3.c",
]);
for src in sources.files.iter() {
cfg.file(Path::new("compiler-rt/lib/builtins").join(src));
}
cfg.compile("libcompiler-rt.a");
}

@ -0,0 +1 @@
Subproject commit 515106ebc07227b85336816ef77bc39506b8fd26

View File

@ -0,0 +1,60 @@
#![feature(lang_items)]
#![no_std]
extern {
fn __ashldi3();
fn __ashrdi3();
fn __divdi3();
fn __divmoddi4();
fn __divmodsi4();
fn __divsi3();
fn __lshrdi3();
fn __moddi3();
fn __modsi3();
fn __muldi3();
fn __mulodi4();
fn __mulosi4();
fn __udivdi3();
fn __udivmoddi4();
fn __udivmodsi4();
fn __udivsi3();
fn __umoddi3();
fn __umodsi3();
fn __addsf3();
fn __adddf3();
}
macro_rules! declare {
($func:ident, $sym:ident) => {
#[no_mangle]
pub extern fn $func() -> usize {
$sym as usize
}
}
}
declare!(___ashldi3, __ashldi3);
declare!(___ashrdi3, __ashrdi3);
declare!(___divdi3, __divdi3);
declare!(___divmoddi4, __divmoddi4);
declare!(___divmodsi4, __divmodsi4);
declare!(___divsi3, __divsi3);
declare!(___lshrdi3, __lshrdi3);
declare!(___moddi3, __moddi3);
declare!(___modsi3, __modsi3);
declare!(___muldi3, __muldi3);
declare!(___mulodi4, __mulodi4);
declare!(___mulosi4, __mulosi4);
declare!(___udivdi3, __udivdi3);
declare!(___udivmoddi4, __udivmoddi4);
declare!(___udivmodsi4, __udivmodsi4);
declare!(___udivsi3, __udivsi3);
declare!(___umoddi3, __umoddi3);
declare!(___umodsi3, __umodsi3);
declare!(___addsf3, __addsf3);
declare!(___adddf3, __adddf3);
#[lang = "eh_personality"]
fn eh_personality() {}
#[lang = "panic_fmt"]
fn panic_fmt() {}

35
compiler-rt/src/lib.rs Normal file
View File

@ -0,0 +1,35 @@
#![feature(drop_types_in_const)]
extern crate libloading;
use std::sync::{Once, ONCE_INIT};
use std::env;
use libloading::Library;
fn compiler_rt() -> &'static Library {
let dir = env::current_exe().unwrap();
let cdylib = dir.parent().unwrap().read_dir().unwrap().map(|c| {
c.unwrap().path()
}).find(|path| {
path.file_name().unwrap().to_str().unwrap().contains("compiler_rt_cdylib")
}).unwrap();
unsafe {
static mut COMPILER_RT: Option<Library> = None;
static INIT: Once = ONCE_INIT;
INIT.call_once(|| {
COMPILER_RT = Some(Library::new(&cdylib).unwrap());
});
COMPILER_RT.as_ref().unwrap()
}
}
pub fn get(sym: &str) -> usize {
unsafe {
let sym = format!("_{}", sym);
let f: fn() -> usize = *compiler_rt().get(sym.as_bytes()).unwrap();
f()
}
}

View File

@ -27,42 +27,13 @@ fn gcc_s() -> &'static Library {
}
#[cfg(windows)]
macro_rules! declare {
($symbol:ident: fn($($i:ty),+) -> $o:ty) => {
pub fn $symbol() -> Option<unsafe extern fn($($i),+) -> $o> {
None
}
}
pub fn get(_sym: &str) -> Option<usize> {
None
}
#[cfg(not(windows))]
macro_rules! declare {
($symbol:ident: fn($($i:ty),+) -> $o:ty) => {
pub fn $symbol() -> Option<unsafe extern fn($($i),+) -> $o> {
unsafe {
gcc_s().get(concat!("__", stringify!($symbol)).as_bytes()).ok().map(|s| *s)
}
}
pub fn get(sym: &str) -> Option<usize> {
unsafe {
gcc_s().get(sym.as_bytes()).ok().map(|s| *s)
}
}
declare!(ashldi3: fn(u64, u32) -> u64);
declare!(ashrdi3: fn(i64, u32) -> i64);
declare!(divdi3: fn(i64, i64) -> i64);
declare!(divmoddi4: fn(i64, i64, &mut i64) -> i64);
declare!(divmodsi4: fn(i32, i32, &mut i32) -> i32);
declare!(divsi3: fn(i32, i32) -> i32);
declare!(lshrdi3: fn(u64, u32) -> u64);
declare!(moddi3: fn(i64, i64) -> i64);
declare!(modsi3: fn(i32, i32) -> i32);
declare!(muldi3: fn(u64, u64) -> u64);
declare!(mulodi4: fn(i64, i64, &mut i32) -> i64);
declare!(mulosi4: fn(i32, i32, &mut i32) -> i32);
declare!(udivdi3: fn(u64, u64) -> u64);
declare!(udivmoddi4: fn(u64, u64, Option<&mut u64>) -> u64);
declare!(udivmodsi4: fn(u32, u32, Option<&mut u32>) -> u32);
declare!(udivsi3: fn(u32, u32) -> u32);
declare!(umoddi3: fn(u64, u64) -> u64);
declare!(umodsi3: fn(u32, u32) -> u32);
declare!(addsf3: fn(f32, f32) -> f32);
declare!(adddf3: fn(f64, f64) -> f64);

View File

@ -89,12 +89,12 @@ macro_rules! add {
if a_exponent.0 == 0 {
let (exponent, significand) = <$ty>::normalize(a_significand.0);
a_exponent = Wrapping(exponent);
a_significand = Wrapping(significand);
a_significand = Wrapping(significand);
}
if b_exponent.0 == 0 {
let (exponent, significand) = <$ty>::normalize(b_significand.0);
b_exponent = Wrapping(exponent);
b_significand = Wrapping(significand);
b_significand = Wrapping(significand);
}
// The sign of the result is the sign of the larger operand, a. If they
@ -123,8 +123,8 @@ macro_rules! add {
if subtraction {
a_significand -= b_significand;
// If a == -b, return +zero.
if a_significand.0 == 0 {
return (<$ty as Float>::from_repr(0));
if a_significand.0 == 0 {
return (<$ty as Float>::from_repr(0));
}
// If partial cancellation occured, we need to left-shift the result
@ -148,7 +148,7 @@ macro_rules! add {
}
// If we have overflowed the type, return +/- infinity:
if a_exponent >= Wrapping(max_exponent.0 as i32) {
if a_exponent >= Wrapping(max_exponent.0 as i32) {
return (<$ty>::from_repr((inf_rep | result_sign).0));
}
@ -190,47 +190,46 @@ mod tests {
use float::Float;
use qc::{U32, U64};
use gcc_s;
use rand;
// NOTE The tests below have special handing for NaN values.
// Because NaN != NaN, the floating-point representations must be used
// Because there are many diffferent values of NaN, and the implementation
// doesn't care about calculating the 'correct' one, if both values are NaN
// the values are considered equivalent.
// TODO: Add F32/F64 to qc so that they print the right values (at the very least)
quickcheck! {
fn addsf3(a: U32, b: U32) -> bool {
let (a, b) = (f32::from_repr(a.0), f32::from_repr(b.0));
let x = super::__addsf3(a, b);
match gcc_s::addsf3() {
// NOTE(cfg) for some reason, on hard float targets, our implementation doesn't
// match the output of its gcc_s counterpart. Until we investigate further, we'll
// just avoid testing against gcc_s on those targets. Do note that our
// implementation matches the output of the FPU instruction on *hard* float targets
// and matches its gcc_s counterpart on *soft* float targets.
#[cfg(not(gnueabihf))]
Some(addsf3) if rand::random() => x.eq_repr(unsafe { addsf3(a, b) }),
_ => x.eq_repr(a + b),
}
}
fn adddf3(a: U64, b: U64) -> bool {
let (a, b) = (f64::from_repr(a.0), f64::from_repr(b.0));
let x = super::__adddf3(a, b);
match gcc_s::adddf3() {
// NOTE(cfg) See NOTE above
#[cfg(not(gnueabihf))]
Some(adddf3) if rand::random() => x.eq_repr(unsafe { adddf3(a, b) }),
_ => x.eq_repr(a + b),
struct FRepr<F>(F);
impl<F: Float> PartialEq for FRepr<F> {
fn eq(&self, other: &FRepr<F>) -> bool {
// NOTE(cfg) for some reason, on hard float targets, our implementation doesn't
// match the output of its gcc_s counterpart. Until we investigate further, we'll
// just avoid testing against gcc_s on those targets. Do note that our
// implementation matches the output of the FPU instruction on *hard* float targets
// and matches its gcc_s counterpart on *soft* float targets.
if cfg!(gnueabihf) {
return true
}
self.0.eq_repr(other.0)
}
}
// TODO: Add F32/F64 to qc so that they print the right values (at the very least)
check! {
fn __addsf3(f: extern fn(f32, f32) -> f32,
a: U32,
b: U32)
-> Option<FRepr<f32> > {
let (a, b) = (f32::from_repr(a.0), f32::from_repr(b.0));
Some(FRepr(f(a, b)))
}
fn __adddf3(f: extern fn(f64, f64) -> f64,
a: U64,
b: U64) -> Option<FRepr<f64> > {
let (a, b) = (f64::from_repr(a.0), f64::from_repr(b.0));
Some(FRepr(f(a, b)))
}
}
// More tests for special float values
#[test]

View File

@ -3,10 +3,10 @@ use core::mem;
pub mod add;
/// Trait for some basic operations on floats
pub trait Float: Sized {
pub trait Float: Sized + Copy {
/// A uint of the same with as the float
type Int;
/// Returns the bitwidth of the float type
fn bits() -> u32;
@ -22,7 +22,7 @@ pub trait Float: Sized {
/// compared.
fn eq_repr(self, rhs: Self) -> bool;
/// Returns a `Self::Int` transmuted back to `Self`
/// Returns a `Self::Int` transmuted back to `Self`
fn from_repr(a: Self::Int) -> Self;
/// Returns (normalized exponent, normalized significand)

View File

@ -74,60 +74,34 @@ mulo!(__mulodi4: i64);
mod tests {
use qc::{I32, I64, U64};
use gcc_s;
use rand;
quickcheck! {
fn muldi(a: U64, b: U64) -> bool {
let (a, b) = (a.0, b.0);
let r = super::__muldi3(a, b);
match gcc_s::muldi3() {
Some(muldi3) if rand::random() => r == unsafe { muldi3(a, b) },
_ => r == a.wrapping_mul(b),
}
check! {
fn __muldi3(f: extern fn(u64, u64) -> u64, a: U64, b: U64)
-> Option<u64> {
Some(f(a.0, b.0))
}
fn mulosi(a: I32, b: I32) -> bool {
fn __mulosi4(f: extern fn(i32, i32, &mut i32) -> i32,
a: I32,
b: I32) -> Option<(i32, i32)> {
let (a, b) = (a.0, b.0);
let mut overflow = 2;
let r = super::__mulosi4(a, b, &mut overflow);
let r = f(a, b, &mut overflow);
if overflow != 0 && overflow != 1 {
return false;
}
match gcc_s::mulosi4() {
Some(mulosi4) if rand::random() => {
let mut gcc_s_overflow = 2;
let gcc_s_r = unsafe {
mulosi4(a, b, &mut gcc_s_overflow)
};
(r, overflow) == (gcc_s_r, gcc_s_overflow)
},
_ => (r, overflow != 0) == a.overflowing_mul(b),
return None
}
Some((r, overflow))
}
fn mulodi(a: I64, b: I64) -> bool {
fn __mulodi4(f: extern fn(i64, i64, &mut i32) -> i64,
a: I64,
b: I64) -> Option<(i64, i32)> {
let (a, b) = (a.0, b.0);
let mut overflow = 2;
let r = super::__mulodi4(a, b, &mut overflow);
let r = f(a, b, &mut overflow);
if overflow != 0 && overflow != 1 {
return false;
}
match gcc_s::mulodi4() {
Some(mulodi4) if rand::random() => {
let mut gcc_s_overflow = 2;
let gcc_s_r = unsafe {
mulodi4(a, b, &mut gcc_s_overflow)
};
(r, overflow) == (gcc_s_r, gcc_s_overflow)
},
_ => (r, overflow != 0) == a.overflowing_mul(b),
return None
}
Some((r, overflow))
}
}
}

View File

@ -54,116 +54,70 @@ divmod!(__divmoddi4, __divdi3: i64);
mod tests {
use qc::{U32, U64};
use gcc_s;
use quickcheck::TestResult;
use rand;
quickcheck!{
fn divdi3(n: U64, d: U64) -> TestResult {
check! {
fn __divdi3(f: extern fn(i64, i64) -> i64, n: U64, d: U64) -> Option<i64> {
let (n, d) = (n.0 as i64, d.0 as i64);
if d == 0 {
TestResult::discard()
None
} else {
let q = super::__divdi3(n, d);
match gcc_s::divdi3() {
Some(divdi3) if rand::random() => {
TestResult::from_bool(q == unsafe { divdi3(n, d) })
},
_ => TestResult::from_bool(q == n / d),
}
Some(f(n, d))
}
}
fn moddi3(n: U64, d: U64) -> TestResult {
fn __moddi3(f: extern fn(i64, i64) -> i64, n: U64, d: U64) -> Option<i64> {
let (n, d) = (n.0 as i64, d.0 as i64);
if d == 0 {
TestResult::discard()
None
} else {
let r = super::__moddi3(n, d);
match gcc_s::moddi3() {
Some(moddi3) if rand::random() => {
TestResult::from_bool(r == unsafe { moddi3(n, d) })
},
_ => TestResult::from_bool(r == n % d),
}
Some(f(n, d))
}
}
fn divmoddi4(n: U64, d: U64) -> TestResult {
fn __divmoddi4(f: extern fn(i64, i64, &mut i64) -> i64,
n: U64,
d: U64) -> Option<(i64, i64)> {
let (n, d) = (n.0 as i64, d.0 as i64);
if d == 0 {
TestResult::discard()
None
} else {
let mut r = 0;
let q = super::__divmoddi4(n, d, &mut r);
match gcc_s::divmoddi4() {
Some(divmoddi4) if rand::random() => {
let mut gcc_s_r = 0;
let gcc_s_q = unsafe {
divmoddi4(n, d, &mut gcc_s_r)
};
TestResult::from_bool(q == gcc_s_q && r == gcc_s_r)
},
_ => TestResult::from_bool(q == n / d && r == n % d),
}
let q = f(n, d, &mut r);
Some((q, r))
}
}
fn divsi3(n: U32, d: U32) -> TestResult {
fn __divsi3(f: extern fn(i32, i32) -> i32,
n: U32,
d: U32) -> Option<i32> {
let (n, d) = (n.0 as i32, d.0 as i32);
if d == 0 {
TestResult::discard()
None
} else {
let q = super::__divsi3(n, d);
match gcc_s::divsi3() {
Some(divsi3) if rand::random() => {
TestResult::from_bool(q == unsafe { divsi3(n, d)})
},
_ => TestResult::from_bool(q == n / d),
}
Some(f(n, d))
}
}
fn modsi3(n: U32, d: U32) -> TestResult {
fn __modsi3(f: extern fn(i32, i32) -> i32,
n: U32,
d: U32) -> Option<i32> {
let (n, d) = (n.0 as i32, d.0 as i32);
if d == 0 {
TestResult::discard()
None
} else {
let r = super::__modsi3(n, d);
match gcc_s::modsi3() {
Some(modsi3) if rand::random() => {
TestResult::from_bool(r == unsafe { modsi3(n, d) })
},
_ => TestResult::from_bool(r == n % d),
}
Some(f(n, d))
}
}
fn divmodsi4(n: U32, d: U32) -> TestResult {
fn __divmodsi4(f: extern fn(i32, i32, &mut i32) -> i32,
n: U32,
d: U32) -> Option<(i32, i32)> {
let (n, d) = (n.0 as i32, d.0 as i32);
if d == 0 {
TestResult::discard()
None
} else {
let mut r = 0;
let q = super::__divmodsi4(n, d, &mut r);
match gcc_s::divmodsi4() {
Some(divmodsi4) if rand::random() => {
let mut gcc_s_r = 0;
let gcc_s_q = unsafe {
divmodsi4(n, d, &mut gcc_s_r)
};
TestResult::from_bool(q == gcc_s_q && r == gcc_s_r)
},
_ => TestResult::from_bool(q == n / d && r == n % d),
}
let q = f(n, d, &mut r);
Some((q, r))
}
}
}

View File

@ -62,57 +62,32 @@ lshr!(__lshrdi3: u64);
mod tests {
use qc::{I64, U64};
use gcc_s;
use quickcheck::TestResult;
use rand;
// NOTE We purposefully stick to `u32` for `b` here because we want "small" values (b < 64)
quickcheck! {
fn ashldi(a: U64, b: u32) -> TestResult {
check! {
fn __ashldi3(f: extern fn(u64, u32) -> u64, a: U64, b: u32) -> Option<u64> {
let a = a.0;
if b >= 64 {
TestResult::discard()
None
} else {
let r = super::__ashldi3(a, b);
match gcc_s::ashldi3() {
Some(ashldi3) if rand::random() => {
TestResult::from_bool(r == unsafe { ashldi3(a, b) })
},
_ => TestResult::from_bool(r == a << b),
}
Some(f(a, b))
}
}
fn ashrdi(a: I64, b: u32) -> TestResult {
fn __ashrdi3(f: extern fn(i64, u32) -> i64, a: I64, b: u32) -> Option<i64> {
let a = a.0;
if b >= 64 {
TestResult::discard()
None
} else {
let r = super::__ashrdi3(a, b);
match gcc_s::ashrdi3() {
Some(ashrdi3) if rand::random() => {
TestResult::from_bool(r == unsafe { ashrdi3(a, b) })
},
_ => TestResult::from_bool(r == a >> b),
}
Some(f(a, b))
}
}
fn lshrdi(a: U64, b: u32) -> TestResult {
fn __lshrdi3(f: extern fn(u64, u32) -> u64, a: U64, b: u32) -> Option<u64> {
let a = a.0;
if b >= 64 {
TestResult::discard()
None
} else {
let r = super::__lshrdi3(a, b);
match gcc_s::lshrdi3() {
Some(lshrdi3) if rand::random() => {
TestResult::from_bool(r == unsafe { lshrdi3(a, b) })
},
_ => TestResult::from_bool(r == a >> b),
}
Some(f(a, b))
}
}
}

View File

@ -230,116 +230,66 @@ pub extern "C" fn __udivmoddi4(n: u64, d: u64, rem: Option<&mut u64>) -> u64 {
mod tests {
use qc::{U32, U64};
use gcc_s;
use quickcheck::TestResult;
use rand;
quickcheck!{
fn udivdi3(n: U64, d: U64) -> TestResult {
check! {
fn __udivdi3(f: extern fn(u64, u64) -> u64, n: U64, d: U64) -> Option<u64> {
let (n, d) = (n.0, d.0);
if d == 0 {
TestResult::discard()
None
} else {
let q = super::__udivdi3(n, d);
match gcc_s::udivdi3() {
Some(udivdi3) if rand::random() => {
TestResult::from_bool(q == unsafe { udivdi3(n, d) })
},
_ => TestResult::from_bool(q == n / d),
}
Some(f(n, d))
}
}
fn umoddi3(n: U64, d: U64) -> TestResult {
fn __umoddi3(f: extern fn(u64, u64) -> u64, n: U64, d: U64) -> Option<u64> {
let (n, d) = (n.0, d.0);
if d == 0 {
TestResult::discard()
None
} else {
let r = super::__umoddi3(n, d);
match gcc_s::umoddi3() {
Some(umoddi3) if rand::random() => {
TestResult::from_bool(r == unsafe { umoddi3(n, d) })
},
_ => TestResult::from_bool(r == n % d),
}
Some(f(n, d))
}
}
fn udivmoddi4(n: U64, d: U64) -> TestResult {
fn __udivmoddi4(f: extern fn(u64, u64, Option<&mut u64>) -> u64,
n: U64,
d: U64) -> Option<(u64, u64)> {
let (n, d) = (n.0, d.0);
if d == 0 {
TestResult::discard()
None
} else {
let mut r = 0;
let q = super::__udivmoddi4(n, d, Some(&mut r));
match gcc_s::udivmoddi4() {
Some(udivmoddi4) if rand::random() => {
let mut gcc_s_r = 0;
let gcc_s_q = unsafe {
udivmoddi4(n, d, Some(&mut gcc_s_r))
};
TestResult::from_bool(q == gcc_s_q && r == gcc_s_r)
},
_ => TestResult::from_bool(q == n / d && r == n % d),
}
let q = f(n, d, Some(&mut r));
Some((q, r))
}
}
fn udivsi3(n: U32, d: U32) -> TestResult {
fn __udivsi3(f: extern fn(u32, u32) -> u32, n: U32, d: U32) -> Option<u32> {
let (n, d) = (n.0, d.0);
if d == 0 {
TestResult::discard()
None
} else {
let q = super::__udivsi3(n, d);
match gcc_s::udivsi3() {
Some(udivsi3) if rand::random() => {
TestResult::from_bool(q == unsafe { udivsi3(n, d) })
},
_ => TestResult::from_bool(q == n / d),
}
Some(f(n, d))
}
}
fn umodsi3(n: U32, d: U32) -> TestResult {
fn __umodsi3(f: extern fn(u32, u32) -> u32, n: U32, d: U32) -> Option<u32> {
let (n, d) = (n.0, d.0);
if d == 0 {
TestResult::discard()
None
} else {
let r = super::__umodsi3(n, d);
match gcc_s::umodsi3() {
Some(umodsi3) if rand::random() => {
TestResult::from_bool(r == unsafe { umodsi3(n, d) })
},
_ => TestResult::from_bool(r == n % d),
}
Some(f(n, d))
}
}
fn udivmodsi4(n: U32, d: U32) -> TestResult {
fn __udivmodsi4(f: extern fn(u32, u32, Option<&mut u32>) -> u32,
n: U32,
d: U32) -> Option<(u32, u32)> {
let (n, d) = (n.0, d.0);
if d == 0 {
TestResult::discard()
None
} else {
let mut r = 0;
let q = super::__udivmodsi4(n, d, Some(&mut r));
match gcc_s::udivmodsi4() {
Some(udivmodsi4) if rand::random() => {
let mut gcc_s_r = 0;
let gcc_s_q = unsafe {
udivmodsi4(n, d, Some(&mut gcc_s_r))
};
TestResult::from_bool(q == gcc_s_q && r == gcc_s_r)
},
_ => TestResult::from_bool(q == n / d && r == n % d),
}
let q = f(n, d, Some(&mut r));
Some((q, r))
}
}
}

View File

@ -20,12 +20,19 @@ extern crate core;
#[cfg(test)]
extern crate gcc_s;
#[cfg(test)]
extern crate compiler_rt;
#[cfg(test)]
extern crate rand;
#[cfg(feature = "weak")]
extern crate rlibc;
#[cfg(test)]
#[macro_use]
mod qc;
pub mod int;
pub mod float;
@ -34,7 +41,3 @@ pub mod arm;
#[cfg(target_arch = "x86_64")]
pub mod x86_64;
#[cfg(test)]
mod qc;

121
src/qc.rs
View File

@ -142,3 +142,124 @@ macro_rules! arbitrary_large {
arbitrary_large!(I64: i64);
arbitrary_large!(U64: u64);
// Convenience macro to test intrinsics against their reference implementations.
//
// Each intrinsic is tested against both the `gcc_s` library as well as
// `compiler-rt`. These libraries are defined in the `gcc_s` crate as well as
// the `compiler-rt` crate in this repository. Both load a dynamic library and
// lookup symbols through that dynamic library to ensure that we're using the
// right intrinsic.
//
// This macro hopefully allows you to define a bare minimum of how to test an
// intrinsic without worrying about these implementation details. A sample
// invocation looks like:
//
//
// check! {
// // First argument is the function we're testing (either from this lib
// // or a dynamically loaded one. Further arguments are all generated by
// // quickcheck.
// fn __my_intrinsic(f: extern fn(i32) -> i32,
// a: I32)
// -> Option<(i32, i64)> {
//
// // Discard tests by returning Some
// if a.0 == 0 {
// return None
// }
//
// // Return the result via `Some` if the test can run
// let mut other_result = 0;
// let result = f(a.0, &mut other_result);
// Some((result, other_result))
// }
// }
//
// If anything returns `None` then the test is discarded, otherwise the two
// results are compared for equality and the test fails if this equality check
// fails.
macro_rules! check {
($(
fn $name:ident($f:ident: extern fn($($farg:ty),*) -> $fret:ty,
$($arg:ident: $t:ty),*)
-> Option<$ret:ty>
{
$($code:tt)*
}
)*) => (
$(
fn $name($f: extern fn($($farg),*) -> $fret,
$($arg: $t),*) -> Option<$ret> {
$($code)*
}
)*
mod _compiler_rt {
use qc::*;
use std::mem;
use quickcheck::TestResult;
$(
#[test]
fn $name() {
fn my_check($($arg:$t),*) -> TestResult {
let my_answer = super::$name(super::super::$name,
$($arg),*);
let compiler_rt_fn = ::compiler_rt::get(stringify!($name));
unsafe {
let compiler_rt_answer =
super::$name(mem::transmute(compiler_rt_fn),
$($arg),*);
match (my_answer, compiler_rt_answer) {
(None, _) | (_, None) => TestResult::discard(),
(Some(a), Some(b)) => {
TestResult::from_bool(a == b)
}
}
}
}
::quickcheck::quickcheck(my_check as fn($($t),*) -> TestResult)
}
)*
}
mod _gcc_s {
use qc::*;
use std::mem;
use quickcheck::TestResult;
$(
#[test]
fn $name() {
fn my_check($($arg:$t),*) -> TestResult {
let my_answer = super::$name(super::super::$name,
$($arg),*);
let gcc_s_fn = ::gcc_s::get(stringify!($name)).unwrap();
unsafe {
let gcc_s_answer =
super::$name(mem::transmute(gcc_s_fn),
$($arg),*);
match (my_answer, gcc_s_answer) {
(None, _) | (_, None) => TestResult::discard(),
(Some(a), Some(b)) => {
TestResult::from_bool(a == b)
}
}
}
}
// If it's not in libgcc, or we couldn't find libgcc, then
// just ignore this. We should have tests through
// compiler-rt in any case
if ::gcc_s::get(stringify!($name)).is_none() {
return
}
::quickcheck::quickcheck(my_check as fn($($t),*) -> TestResult)
}
)*
}
)
}

View File

@ -1,3 +1,5 @@
#![allow(unused_imports)]
use core::intrinsics;
// NOTE These functions are implemented using assembly because they using a custom