Rework how testing is done
Use a build script to generate musl reference outputs and then ensure that everything gets hooked up to actually run reference tests.
This commit is contained in:
parent
575e81ca56
commit
28c69b4197
@ -8,20 +8,19 @@ license = "MIT OR Apache-2.0"
|
||||
name = "libm"
|
||||
repository = "https://github.com/japaric/libm"
|
||||
version = "0.1.2"
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
# only used to run our test suite
|
||||
checked = []
|
||||
default = ['stable']
|
||||
stable = []
|
||||
musl-reference-tests = ['rand']
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/compiler-builtins-smoke-test",
|
||||
"crates/input-generator",
|
||||
"crates/musl-generator",
|
||||
"crates/shared",
|
||||
]
|
||||
|
||||
[dev-dependencies]
|
||||
shared = { path = "shared" }
|
||||
[build-dependencies]
|
||||
rand = { version = "0.6.5", optional = true }
|
||||
|
344
build.rs
Normal file
344
build.rs
Normal file
@ -0,0 +1,344 @@
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
#[cfg(feature = "musl-reference-tests")]
|
||||
musl_reference_tests::generate();
|
||||
}
|
||||
|
||||
#[cfg(feature = "musl-reference-tests")]
|
||||
mod musl_reference_tests {
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::Rng;
|
||||
use std::fs;
|
||||
use std::process::Command;
|
||||
|
||||
// Number of tests to generate for each function
|
||||
const NTESTS: usize = 500;
|
||||
|
||||
// These files are all internal functions or otherwise miscellaneous, not
|
||||
// defining a function we want to test.
|
||||
const IGNORED_FILES: &[&str] = &[
|
||||
"expo2.rs",
|
||||
"fenv.rs",
|
||||
"k_cos.rs",
|
||||
"k_cosf.rs",
|
||||
"k_expo2.rs",
|
||||
"k_expo2f.rs",
|
||||
"k_sin.rs",
|
||||
"k_sinf.rs",
|
||||
"k_tan.rs",
|
||||
"k_tanf.rs",
|
||||
"mod.rs",
|
||||
"rem_pio2.rs",
|
||||
"rem_pio2_large.rs",
|
||||
"rem_pio2f.rs",
|
||||
];
|
||||
|
||||
struct Function {
|
||||
name: String,
|
||||
args: Vec<Ty>,
|
||||
ret: Ty,
|
||||
tests: Vec<Test>,
|
||||
}
|
||||
|
||||
enum Ty {
|
||||
F32,
|
||||
F64,
|
||||
I32,
|
||||
Bool,
|
||||
}
|
||||
|
||||
struct Test {
|
||||
inputs: Vec<i64>,
|
||||
output: i64,
|
||||
}
|
||||
|
||||
pub fn generate() {
|
||||
let files = fs::read_dir("src/math")
|
||||
.unwrap()
|
||||
.map(|f| f.unwrap().path())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut math = Vec::new();
|
||||
for file in files {
|
||||
if IGNORED_FILES.iter().any(|f| file.ends_with(f)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
println!("generating musl reference tests in {:?}", file);
|
||||
|
||||
let contents = fs::read_to_string(file).unwrap();
|
||||
let mut functions = contents.lines().filter(|f| f.starts_with("pub fn"));
|
||||
let function_to_test = functions.next().unwrap();
|
||||
if functions.next().is_some() {
|
||||
panic!("more than one function in");
|
||||
}
|
||||
|
||||
math.push(parse(function_to_test));
|
||||
}
|
||||
|
||||
// Generate a bunch of random inputs for each function. This will
|
||||
// attempt to generate a good set of uniform test cases for exercising
|
||||
// all the various functionality.
|
||||
generate_random_tests(&mut math, &mut rand::thread_rng());
|
||||
|
||||
// After we have all our inputs, use the x86_64-unknown-linux-musl
|
||||
// target to generate the expected output.
|
||||
generate_test_outputs(&mut math);
|
||||
|
||||
// ... and now that we have both inputs and expected outputs, do a bunch
|
||||
// of codegen to create the unit tests which we'll actually execute.
|
||||
generate_unit_tests(&math);
|
||||
}
|
||||
|
||||
/// A "poor man's" parser for the signature of a function
|
||||
fn parse(s: &str) -> Function {
|
||||
let s = eat(s, "pub fn ");
|
||||
let pos = s.find('(').unwrap();
|
||||
let name = &s[..pos];
|
||||
let s = &s[pos + 1..];
|
||||
let end = s.find(')').unwrap();
|
||||
let args = s[..end]
|
||||
.split(',')
|
||||
.map(|arg| {
|
||||
let colon = arg.find(':').unwrap();
|
||||
parse_ty(arg[colon + 1..].trim())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let tail = &s[end + 1..];
|
||||
let tail = eat(tail, " -> ");
|
||||
let ret = parse_ty(tail.trim().split(' ').next().unwrap());
|
||||
|
||||
return Function {
|
||||
name: name.to_string(),
|
||||
args,
|
||||
ret,
|
||||
tests: Vec::new(),
|
||||
};
|
||||
|
||||
fn parse_ty(s: &str) -> Ty {
|
||||
match s {
|
||||
"f32" => Ty::F32,
|
||||
"f64" => Ty::F64,
|
||||
"i32" => Ty::I32,
|
||||
"bool" => Ty::Bool,
|
||||
other => panic!("unknown type `{}`", other),
|
||||
}
|
||||
}
|
||||
|
||||
fn eat<'a>(s: &'a str, prefix: &str) -> &'a str {
|
||||
if s.starts_with(prefix) {
|
||||
&s[prefix.len()..]
|
||||
} else {
|
||||
panic!("{:?} didn't start with {:?}", s, prefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_random_tests<R: Rng>(functions: &mut [Function], rng: &mut R) {
|
||||
for function in functions {
|
||||
for _ in 0..NTESTS {
|
||||
function.tests.push(generate_test(&function.args, rng));
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_test<R: Rng>(args: &[Ty], rng: &mut R) -> Test {
|
||||
let inputs = args.iter().map(|ty| ty.gen_i64(rng)).collect();
|
||||
// zero output for now since we'll generate it later
|
||||
Test { inputs, output: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Ty {
|
||||
fn gen_i64<R: Rng>(&self, r: &mut R) -> i64 {
|
||||
match self {
|
||||
Ty::F32 => r.gen::<f32>().to_bits().into(),
|
||||
Ty::F64 => r.gen::<f64>().to_bits() as i64,
|
||||
Ty::I32 => {
|
||||
if r.gen_range(0, 10) < 1 {
|
||||
let i = *[i32::max_value(), 0, i32::min_value()].choose(r).unwrap();
|
||||
i.into()
|
||||
} else {
|
||||
r.gen::<i32>().into()
|
||||
}
|
||||
}
|
||||
Ty::Bool => r.gen::<bool>() as i64,
|
||||
}
|
||||
}
|
||||
|
||||
fn libc_ty(&self) -> &'static str {
|
||||
match self {
|
||||
Ty::F32 => "f32",
|
||||
Ty::F64 => "f64",
|
||||
Ty::I32 => "i32",
|
||||
Ty::Bool => "i32",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_test_outputs(functions: &mut [Function]) {
|
||||
let mut src = String::new();
|
||||
let dst = std::env::var("OUT_DIR").unwrap();
|
||||
|
||||
// Generate a program which will run all tests with all inputs in
|
||||
// `functions`. This program will write all outputs to stdout (in a
|
||||
// binary format).
|
||||
src.push_str("use std::io::Write;");
|
||||
src.push_str("fn main() {");
|
||||
src.push_str("let mut result = Vec::new();");
|
||||
for function in functions.iter_mut() {
|
||||
src.push_str("unsafe {");
|
||||
src.push_str("extern { fn ");
|
||||
src.push_str(&function.name);
|
||||
src.push_str("(");
|
||||
for (i, arg) in function.args.iter().enumerate() {
|
||||
src.push_str(&format!("arg{}: {},", i, arg.libc_ty()));
|
||||
}
|
||||
src.push_str(") -> ");
|
||||
src.push_str(function.ret.libc_ty());
|
||||
src.push_str("; }");
|
||||
|
||||
src.push_str(&format!("static TESTS: &[[i64; {}]]", function.args.len()));
|
||||
src.push_str(" = &[");
|
||||
for test in function.tests.iter() {
|
||||
src.push_str("[");
|
||||
for val in test.inputs.iter() {
|
||||
src.push_str(&val.to_string());
|
||||
src.push_str(",");
|
||||
}
|
||||
src.push_str("],");
|
||||
}
|
||||
src.push_str("];");
|
||||
|
||||
src.push_str("for test in TESTS {");
|
||||
src.push_str("let output = ");
|
||||
src.push_str(&function.name);
|
||||
src.push_str("(");
|
||||
for (i, arg) in function.args.iter().enumerate() {
|
||||
src.push_str(&match arg {
|
||||
Ty::F32 => format!("f32::from_bits(test[{}] as u32)", i),
|
||||
Ty::F64 => format!("f64::from_bits(test[{}] as u64)", i),
|
||||
Ty::I32 => format!("test[{}] as i32", i),
|
||||
Ty::Bool => format!("test[{}] as i32", i),
|
||||
});
|
||||
src.push_str(",");
|
||||
}
|
||||
src.push_str(");");
|
||||
src.push_str("let output = ");
|
||||
src.push_str(match function.ret {
|
||||
Ty::F32 => "output.to_bits() as i64",
|
||||
Ty::F64 => "output.to_bits() as i64",
|
||||
Ty::I32 => "output as i64",
|
||||
Ty::Bool => "output as i64",
|
||||
});
|
||||
src.push_str(";");
|
||||
src.push_str("result.extend_from_slice(&output.to_le_bytes());");
|
||||
|
||||
src.push_str("}");
|
||||
|
||||
src.push_str("}");
|
||||
}
|
||||
|
||||
src.push_str("std::io::stdout().write_all(&result).unwrap();");
|
||||
|
||||
src.push_str("}");
|
||||
|
||||
let path = format!("{}/gen.rs", dst);
|
||||
fs::write(&path, src).unwrap();
|
||||
|
||||
// Make it somewhat pretty if something goes wrong
|
||||
drop(Command::new("rustfmt").arg(&path).status());
|
||||
|
||||
// Compile and execute this tests for the musl target, assuming we're an
|
||||
// x86_64 host effectively.
|
||||
let status = Command::new("rustc")
|
||||
.current_dir(&dst)
|
||||
.arg(&path)
|
||||
.arg("--target=x86_64-unknown-linux-musl")
|
||||
.status()
|
||||
.unwrap();
|
||||
assert!(status.success());
|
||||
let output = Command::new("./gen")
|
||||
.current_dir(&dst)
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(output.status.success());
|
||||
assert!(output.stderr.is_empty());
|
||||
|
||||
// Map all the output bytes back to an `i64` and then shove it all into
|
||||
// the expected results.
|
||||
let mut results =
|
||||
output.stdout.chunks_exact(8)
|
||||
.map(|buf| {
|
||||
let mut exact = [0; 8];
|
||||
exact.copy_from_slice(buf);
|
||||
i64::from_le_bytes(exact)
|
||||
});
|
||||
|
||||
for test in functions.iter_mut().flat_map(|f| f.tests.iter_mut()) {
|
||||
test.output = results.next().unwrap();
|
||||
}
|
||||
assert!(results.next().is_none());
|
||||
}
|
||||
|
||||
/// Codegens a file which has a ton of `#[test]` annotations for all the
|
||||
/// tests that we generated above.
|
||||
fn generate_unit_tests(functions: &[Function]) {
|
||||
let mut src = String::new();
|
||||
let dst = std::env::var("OUT_DIR").unwrap();
|
||||
|
||||
for function in functions {
|
||||
src.push_str("#[test]");
|
||||
src.push_str("fn ");
|
||||
src.push_str(&function.name);
|
||||
src.push_str("_matches_musl() {");
|
||||
src.push_str(&format!("static TESTS: &[([i64; {}], i64)]", function.args.len()));
|
||||
src.push_str(" = &[");
|
||||
for test in function.tests.iter() {
|
||||
src.push_str("([");
|
||||
for val in test.inputs.iter() {
|
||||
src.push_str(&val.to_string());
|
||||
src.push_str(",");
|
||||
}
|
||||
src.push_str("],");
|
||||
src.push_str(&test.output.to_string());
|
||||
src.push_str("),");
|
||||
}
|
||||
src.push_str("];");
|
||||
|
||||
src.push_str("for (test, expected) in TESTS {");
|
||||
src.push_str("let output = ");
|
||||
src.push_str(&function.name);
|
||||
src.push_str("(");
|
||||
for (i, arg) in function.args.iter().enumerate() {
|
||||
src.push_str(&match arg {
|
||||
Ty::F32 => format!("f32::from_bits(test[{}] as u32)", i),
|
||||
Ty::F64 => format!("f64::from_bits(test[{}] as u64)", i),
|
||||
Ty::I32 => format!("test[{}] as i32", i),
|
||||
Ty::Bool => format!("test[{}] as i32", i),
|
||||
});
|
||||
src.push_str(",");
|
||||
}
|
||||
src.push_str(");");
|
||||
src.push_str(match function.ret {
|
||||
Ty::F32 => "if _eqf(output, f32::from_bits(*expected as u32)).is_ok() { continue }",
|
||||
Ty::F64 => "if _eq(output, f64::from_bits(*expected as u64)).is_ok() { continue }",
|
||||
Ty::I32 => "if output as i64 == expected { continue }",
|
||||
Ty::Bool => unreachable!(),
|
||||
});
|
||||
|
||||
src.push_str(r#"
|
||||
panic!("INPUT: {:?} EXPECTED: {:?} ACTUAL {:?}", test, expected, output);
|
||||
"#);
|
||||
src.push_str("}");
|
||||
|
||||
src.push_str("}");
|
||||
}
|
||||
|
||||
let path = format!("{}/tests.rs", dst);
|
||||
fs::write(&path, src).unwrap();
|
||||
|
||||
// Try to make it somewhat pretty
|
||||
drop(Command::new("rustfmt").arg(&path).status());
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
[package]
|
||||
name = "input-generator"
|
||||
version = "0.1.0"
|
||||
authors = ["Jorge Aparicio <jorge@japaric.io>"]
|
||||
|
||||
[dependencies]
|
||||
rand = "0.5.4"
|
@ -1,189 +0,0 @@
|
||||
extern crate rand;
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
use std::error::Error;
|
||||
use std::fs::{self, File};
|
||||
use std::io::Write;
|
||||
|
||||
use rand::{RngCore, SeedableRng, XorShiftRng};
|
||||
|
||||
const NTESTS: usize = 10_000;
|
||||
|
||||
fn main() -> Result<(), Box<Error>> {
|
||||
let mut rng = XorShiftRng::from_rng(&mut rand::thread_rng())?;
|
||||
|
||||
fs::remove_dir_all("bin").ok();
|
||||
fs::create_dir_all("bin/input")?;
|
||||
fs::create_dir_all("bin/output")?;
|
||||
|
||||
f32(&mut rng)?;
|
||||
f32f32(&mut rng)?;
|
||||
f32f32f32(&mut rng)?;
|
||||
f32i16(&mut rng)?;
|
||||
f64(&mut rng)?;
|
||||
f64f64(&mut rng)?;
|
||||
f64f64f64(&mut rng)?;
|
||||
f64i16(&mut rng)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn f32(rng: &mut XorShiftRng) -> Result<(), Box<Error>> {
|
||||
let mut set = BTreeSet::new();
|
||||
|
||||
while set.len() < NTESTS {
|
||||
let f = f32::from_bits(rng.next_u32());
|
||||
|
||||
if f.is_nan() {
|
||||
continue;
|
||||
}
|
||||
|
||||
set.insert(f.to_bits());
|
||||
}
|
||||
|
||||
let mut f = File::create("bin/input/f32")?;
|
||||
for i in set {
|
||||
f.write_all(&i.to_le_bytes())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn f32f32(rng: &mut XorShiftRng) -> Result<(), Box<Error>> {
|
||||
let mut f = File::create("bin/input/f32f32")?;
|
||||
let mut i = 0;
|
||||
while i < NTESTS {
|
||||
let x0 = f32::from_bits(rng.next_u32());
|
||||
let x1 = f32::from_bits(rng.next_u32());
|
||||
|
||||
if x0.is_nan() || x1.is_nan() {
|
||||
continue;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
f.write_all(&x0.to_bits().to_le_bytes())?;
|
||||
f.write_all(&x1.to_bits().to_le_bytes())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn f32i16(rng: &mut XorShiftRng) -> Result<(), Box<Error>> {
|
||||
let mut f = File::create("bin/input/f32i16")?;
|
||||
let mut i = 0;
|
||||
while i < NTESTS {
|
||||
let x0 = f32::from_bits(rng.next_u32());
|
||||
let x1 = rng.next_u32() as i16;
|
||||
|
||||
if x0.is_nan() {
|
||||
continue;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
f.write_all(&x0.to_bits().to_le_bytes())?;
|
||||
f.write_all(&x1.to_le_bytes())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn f32f32f32(rng: &mut XorShiftRng) -> Result<(), Box<Error>> {
|
||||
let mut f = File::create("bin/input/f32f32f32")?;
|
||||
let mut i = 0;
|
||||
while i < NTESTS {
|
||||
let x0 = f32::from_bits(rng.next_u32());
|
||||
let x1 = f32::from_bits(rng.next_u32());
|
||||
let x2 = f32::from_bits(rng.next_u32());
|
||||
|
||||
if x0.is_nan() || x1.is_nan() || x2.is_nan() {
|
||||
continue;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
f.write_all(&x0.to_bits().to_le_bytes())?;
|
||||
f.write_all(&x1.to_bits().to_le_bytes())?;
|
||||
f.write_all(&x2.to_bits().to_le_bytes())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn f64(rng: &mut XorShiftRng) -> Result<(), Box<Error>> {
|
||||
let mut set = BTreeSet::new();
|
||||
|
||||
while set.len() < NTESTS {
|
||||
let f = f64::from_bits(rng.next_u64());
|
||||
|
||||
if f.is_nan() {
|
||||
continue;
|
||||
}
|
||||
|
||||
set.insert(f.to_bits());
|
||||
}
|
||||
|
||||
let mut f = File::create("bin/input/f64")?;
|
||||
for i in set {
|
||||
f.write_all(&i.to_le_bytes())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn f64f64(rng: &mut XorShiftRng) -> Result<(), Box<Error>> {
|
||||
let mut f = File::create("bin/input/f64f64")?;
|
||||
let mut i = 0;
|
||||
while i < NTESTS {
|
||||
let x0 = f64::from_bits(rng.next_u64());
|
||||
let x1 = f64::from_bits(rng.next_u64());
|
||||
|
||||
if x0.is_nan() || x1.is_nan() {
|
||||
continue;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
f.write_all(&x0.to_bits().to_le_bytes())?;
|
||||
f.write_all(&x1.to_bits().to_le_bytes())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn f64f64f64(rng: &mut XorShiftRng) -> Result<(), Box<Error>> {
|
||||
let mut f = File::create("bin/input/f64f64f64")?;
|
||||
let mut i = 0;
|
||||
while i < NTESTS {
|
||||
let x0 = f64::from_bits(rng.next_u64());
|
||||
let x1 = f64::from_bits(rng.next_u64());
|
||||
let x2 = f64::from_bits(rng.next_u64());
|
||||
|
||||
if x0.is_nan() || x1.is_nan() || x2.is_nan() {
|
||||
continue;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
f.write_all(&x0.to_bits().to_le_bytes())?;
|
||||
f.write_all(&x1.to_bits().to_le_bytes())?;
|
||||
f.write_all(&x2.to_bits().to_le_bytes())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn f64i16(rng: &mut XorShiftRng) -> Result<(), Box<Error>> {
|
||||
let mut f = File::create("bin/input/f64i16")?;
|
||||
let mut i = 0;
|
||||
while i < NTESTS {
|
||||
let x0 = f64::from_bits(rng.next_u64());
|
||||
let x1 = rng.next_u32() as i16;
|
||||
|
||||
if x0.is_nan() {
|
||||
continue;
|
||||
}
|
||||
|
||||
i += 1;
|
||||
f.write_all(&x0.to_bits().to_le_bytes())?;
|
||||
f.write_all(&x1.to_le_bytes())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
[package]
|
||||
name = "musl-generator"
|
||||
version = "0.1.0"
|
||||
authors = ["Jorge Aparicio <jorge@japaric.io>"]
|
||||
|
||||
[dependencies]
|
||||
lazy_static = "1.0.2"
|
||||
shared = { path = "../shared" }
|
||||
libm = { path = ".." }
|
@ -1,191 +0,0 @@
|
||||
macro_rules! f32 {
|
||||
($($fun:ident,)+) => {{
|
||||
$(
|
||||
// check type signature
|
||||
let _: fn(f32) -> f32 = libm::$fun;
|
||||
let mut $fun = File::create(concat!("bin/output/musl.", stringify!($fun)))?;
|
||||
)+
|
||||
|
||||
for x in shared::F32.iter() {
|
||||
$(
|
||||
let y = unsafe {
|
||||
extern "C" {
|
||||
fn $fun(_: f32) -> f32;
|
||||
}
|
||||
|
||||
$fun(*x)
|
||||
};
|
||||
|
||||
$fun.write_all(&y.to_bits().to_le_bytes())?;
|
||||
)+
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! f32f32 {
|
||||
($($fun:ident,)+) => {{
|
||||
$(
|
||||
// check type signature
|
||||
let _: fn(f32, f32) -> f32 = libm::$fun;
|
||||
let mut $fun = File::create(concat!("bin/output/musl.", stringify!($fun)))?;
|
||||
)+
|
||||
|
||||
for (x0, x1) in shared::F32F32.iter() {
|
||||
$(
|
||||
let y = unsafe {
|
||||
extern "C" {
|
||||
fn $fun(_: f32, _: f32) -> f32;
|
||||
}
|
||||
|
||||
$fun(*x0, *x1)
|
||||
};
|
||||
|
||||
$fun.write_all(&y.to_bits().to_le_bytes())?;
|
||||
)+
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! f32f32f32 {
|
||||
($($fun:ident,)+) => {{
|
||||
$(
|
||||
// check type signature
|
||||
let _: fn(f32, f32, f32) -> f32 = libm::$fun;
|
||||
let mut $fun = File::create(concat!("bin/output/musl.", stringify!($fun)))?;
|
||||
)+
|
||||
|
||||
for (x0, x1, x2) in shared::F32F32F32.iter() {
|
||||
$(
|
||||
let y = unsafe {
|
||||
extern "C" {
|
||||
fn $fun(_: f32, _: f32, _: f32) -> f32;
|
||||
}
|
||||
|
||||
$fun(*x0, *x1, *x2)
|
||||
};
|
||||
|
||||
$fun.write_all(&y.to_bits().to_le_bytes())?;
|
||||
)+
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! f32i32 {
|
||||
($($fun:ident,)+) => {{
|
||||
$(
|
||||
// check type signature
|
||||
let _: fn(f32, i32) -> f32 = libm::$fun;
|
||||
let mut $fun = File::create(concat!("bin/output/musl.", stringify!($fun)))?;
|
||||
)+
|
||||
|
||||
for (x0, x1) in shared::F32I32.iter() {
|
||||
$(
|
||||
let y = unsafe {
|
||||
extern "C" {
|
||||
fn $fun(_: f32, _: i32) -> f32;
|
||||
}
|
||||
|
||||
$fun(*x0, *x1 as i32)
|
||||
};
|
||||
|
||||
$fun.write_all(&y.to_bits().to_le_bytes())?;
|
||||
)+
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! f64 {
|
||||
($($fun:ident,)+) => {{
|
||||
$(
|
||||
// check type signature
|
||||
let _: fn(f64) -> f64 = libm::$fun;
|
||||
let mut $fun = File::create(concat!("bin/output/musl.", stringify!($fun)))?;
|
||||
)+
|
||||
|
||||
for x in shared::F64.iter() {
|
||||
$(
|
||||
let y = unsafe {
|
||||
extern "C" {
|
||||
fn $fun(_: f64) -> f64;
|
||||
}
|
||||
|
||||
$fun(*x)
|
||||
};
|
||||
|
||||
$fun.write_all(&y.to_bits().to_le_bytes())?;
|
||||
)+
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! f64f64 {
|
||||
($($fun:ident,)+) => {{
|
||||
$(
|
||||
// check type signature
|
||||
let _: fn(f64, f64) -> f64 = libm::$fun;
|
||||
let mut $fun = File::create(concat!("bin/output/musl.", stringify!($fun)))?;
|
||||
)+
|
||||
|
||||
for (x0, x1) in shared::F64F64.iter() {
|
||||
$(
|
||||
let y = unsafe {
|
||||
extern "C" {
|
||||
fn $fun(_: f64, _: f64) -> f64;
|
||||
}
|
||||
|
||||
$fun(*x0, *x1)
|
||||
};
|
||||
|
||||
$fun.write_all(&y.to_bits().to_le_bytes())?;
|
||||
)+
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! f64f64f64 {
|
||||
($($fun:ident,)+) => {{
|
||||
$(
|
||||
// check type signature
|
||||
let _: fn(f64, f64, f64) -> f64 = libm::$fun;
|
||||
let mut $fun = File::create(concat!("bin/output/musl.", stringify!($fun)))?;
|
||||
)+
|
||||
|
||||
for (x0, x1, x2) in shared::F64F64F64.iter() {
|
||||
$(
|
||||
let y = unsafe {
|
||||
extern "C" {
|
||||
fn $fun(_: f64, _: f64, _: f64) -> f64;
|
||||
}
|
||||
|
||||
$fun(*x0, *x1, *x2)
|
||||
};
|
||||
|
||||
$fun.write_all(&y.to_bits().to_le_bytes())?;
|
||||
)+
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! f64i32 {
|
||||
($($fun:ident,)+) => {{
|
||||
$(
|
||||
// check type signature
|
||||
let _: fn(f64, i32) -> f64 = libm::$fun;
|
||||
let mut $fun = File::create(concat!("bin/output/musl.", stringify!($fun)))?;
|
||||
)+
|
||||
|
||||
for (x0, x1) in shared::F64I32.iter() {
|
||||
$(
|
||||
let y = unsafe {
|
||||
extern "C" {
|
||||
fn $fun(_: f64, _: i32) -> f64;
|
||||
}
|
||||
|
||||
$fun(*x0, *x1 as i32)
|
||||
};
|
||||
|
||||
$fun.write_all(&y.to_bits().to_le_bytes())?;
|
||||
)+
|
||||
}
|
||||
}};
|
||||
}
|
@ -1,97 +0,0 @@
|
||||
extern crate libm;
|
||||
extern crate shared;
|
||||
|
||||
use std::error::Error;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
fn main() -> Result<(), Box<Error>> {
|
||||
f32! {
|
||||
acosf,
|
||||
asinf,
|
||||
atanf,
|
||||
cbrtf,
|
||||
ceilf,
|
||||
cosf,
|
||||
coshf,
|
||||
exp2f,
|
||||
expf,
|
||||
expm1f,
|
||||
fabsf,
|
||||
floorf,
|
||||
log10f,
|
||||
log1pf,
|
||||
log2f,
|
||||
logf,
|
||||
roundf,
|
||||
sinf,
|
||||
sinhf,
|
||||
sqrtf,
|
||||
tanf,
|
||||
tanhf,
|
||||
truncf,
|
||||
}
|
||||
|
||||
f32f32! {
|
||||
atan2f,
|
||||
fdimf,
|
||||
fmodf,
|
||||
hypotf,
|
||||
powf,
|
||||
}
|
||||
|
||||
f32i32! {
|
||||
scalbnf,
|
||||
}
|
||||
|
||||
f32f32f32! {
|
||||
fmaf,
|
||||
}
|
||||
|
||||
f64! {
|
||||
acos,
|
||||
asin,
|
||||
atan,
|
||||
cbrt,
|
||||
ceil,
|
||||
cos,
|
||||
cosh,
|
||||
exp,
|
||||
exp2,
|
||||
expm1,
|
||||
fabs,
|
||||
floor,
|
||||
log,
|
||||
log10,
|
||||
log1p,
|
||||
log2,
|
||||
round,
|
||||
sin,
|
||||
sinh,
|
||||
sqrt,
|
||||
tan,
|
||||
tanh,
|
||||
trunc,
|
||||
}
|
||||
|
||||
f64f64! {
|
||||
atan2,
|
||||
fdim,
|
||||
fmod,
|
||||
hypot,
|
||||
pow,
|
||||
}
|
||||
|
||||
f64i32! {
|
||||
scalbn,
|
||||
}
|
||||
|
||||
f64f64f64! {
|
||||
fma,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
[package]
|
||||
name = "shared"
|
||||
version = "0.1.0"
|
||||
authors = ["Jorge Aparicio <jorge@japaric.io>"]
|
||||
|
||||
[dependencies]
|
||||
lazy_static = "1.0.2"
|
@ -1,469 +0,0 @@
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref F32: Vec<f32> = {
|
||||
let bytes = include_bytes!("../../bin/input/f32");
|
||||
|
||||
bytes
|
||||
.chunks_exact(4)
|
||||
.map(|chunk| {
|
||||
let mut buf = [0; 4];
|
||||
buf.copy_from_slice(chunk);
|
||||
f32::from_bits(u32::from_le(u32::from_le_bytes(buf)))
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
pub static ref F32F32: Vec<(f32, f32)> = {
|
||||
let bytes = include_bytes!("../../bin/input/f32f32");
|
||||
|
||||
bytes
|
||||
.chunks_exact(8)
|
||||
.map(|chunk| {
|
||||
let mut x0 = [0; 4];
|
||||
let mut x1 = [0; 4];
|
||||
x0.copy_from_slice(&chunk[..4]);
|
||||
x1.copy_from_slice(&chunk[4..]);
|
||||
|
||||
(
|
||||
f32::from_bits(u32::from_le(u32::from_le_bytes(x0))),
|
||||
f32::from_bits(u32::from_le(u32::from_le_bytes(x1))),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
pub static ref F32F32F32: Vec<(f32, f32, f32)> = {
|
||||
let bytes = include_bytes!("../../bin/input/f32f32f32");
|
||||
|
||||
bytes
|
||||
.chunks_exact(12)
|
||||
.map(|chunk| {
|
||||
let mut x0 = [0; 4];
|
||||
let mut x1 = [0; 4];
|
||||
let mut x2 = [0; 4];
|
||||
x0.copy_from_slice(&chunk[..4]);
|
||||
x1.copy_from_slice(&chunk[4..8]);
|
||||
x2.copy_from_slice(&chunk[8..]);
|
||||
|
||||
(
|
||||
f32::from_bits(u32::from_le(u32::from_le_bytes(x0))),
|
||||
f32::from_bits(u32::from_le(u32::from_le_bytes(x1))),
|
||||
f32::from_bits(u32::from_le(u32::from_le_bytes(x2))),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
pub static ref F32I32: Vec<(f32, i32)> = {
|
||||
let bytes = include_bytes!("../../bin/input/f32i16");
|
||||
|
||||
bytes
|
||||
.chunks_exact(6)
|
||||
.map(|chunk| {
|
||||
let mut x0 = [0; 4];
|
||||
let mut x1 = [0; 2];
|
||||
x0.copy_from_slice(&chunk[..4]);
|
||||
x1.copy_from_slice(&chunk[4..]);
|
||||
|
||||
(
|
||||
f32::from_bits(u32::from_le(u32::from_le_bytes(x0))),
|
||||
i16::from_le(i16::from_le_bytes(x1)) as i32,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
pub static ref F64: Vec<f64> = {
|
||||
let bytes = include_bytes!("../../bin/input/f64");
|
||||
|
||||
bytes
|
||||
.chunks_exact(8)
|
||||
.map(|chunk| {
|
||||
let mut buf = [0; 8];
|
||||
buf.copy_from_slice(chunk);
|
||||
f64::from_bits(u64::from_le(u64::from_le_bytes(buf)))
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
pub static ref F64F64: Vec<(f64, f64)> = {
|
||||
let bytes = include_bytes!("../../bin/input/f64f64");
|
||||
|
||||
bytes
|
||||
.chunks_exact(16)
|
||||
.map(|chunk| {
|
||||
let mut x0 = [0; 8];
|
||||
let mut x1 = [0; 8];
|
||||
x0.copy_from_slice(&chunk[..8]);
|
||||
x1.copy_from_slice(&chunk[8..]);
|
||||
|
||||
(
|
||||
f64::from_bits(u64::from_le(u64::from_le_bytes(x0))),
|
||||
f64::from_bits(u64::from_le(u64::from_le_bytes(x1))),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
pub static ref F64F64F64: Vec<(f64, f64, f64)> = {
|
||||
let bytes = include_bytes!("../../bin/input/f64f64f64");
|
||||
|
||||
bytes
|
||||
.chunks_exact(24)
|
||||
.map(|chunk| {
|
||||
let mut x0 = [0; 8];
|
||||
let mut x1 = [0; 8];
|
||||
let mut x2 = [0; 8];
|
||||
x0.copy_from_slice(&chunk[..8]);
|
||||
x1.copy_from_slice(&chunk[8..16]);
|
||||
x2.copy_from_slice(&chunk[16..]);
|
||||
|
||||
(
|
||||
f64::from_bits(u64::from_le(u64::from_le_bytes(x0))),
|
||||
f64::from_bits(u64::from_le(u64::from_le_bytes(x1))),
|
||||
f64::from_bits(u64::from_le(u64::from_le_bytes(x2))),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
pub static ref F64I32: Vec<(f64, i32)> = {
|
||||
let bytes = include_bytes!("../../bin/input/f64i16");
|
||||
|
||||
bytes
|
||||
.chunks_exact(10)
|
||||
.map(|chunk| {
|
||||
let mut x0 = [0; 8];
|
||||
let mut x1 = [0; 2];
|
||||
x0.copy_from_slice(&chunk[..8]);
|
||||
x1.copy_from_slice(&chunk[8..]);
|
||||
|
||||
(
|
||||
f64::from_bits(u64::from_le(u64::from_le_bytes(x0))),
|
||||
i16::from_le(i16::from_le_bytes(x1)) as i32,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! f32 {
|
||||
($lib:expr, $($fun:ident),+) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $fun() {
|
||||
let expected = include_bytes!(concat!("../bin/output/", $lib, ".", stringify!($fun)))
|
||||
.chunks_exact(4)
|
||||
.map(|chunk| {
|
||||
let mut buf = [0; 4];
|
||||
buf.copy_from_slice(chunk);
|
||||
f32::from_bits(u32::from_le(u32::from_le_bytes(buf)))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (input, expected) in $crate::F32.iter().zip(&expected) {
|
||||
if let Ok(output) = panic::catch_unwind(|| libm::$fun(*input)) {
|
||||
if let Err(error) = libm::_eqf(output, *expected) {
|
||||
panic!(
|
||||
"INPUT: {:#x}, OUTPUT: {:#x}, EXPECTED: {:#x}, ERROR: {}",
|
||||
input.to_bits(),
|
||||
output.to_bits(),
|
||||
expected.to_bits(),
|
||||
error
|
||||
);
|
||||
}
|
||||
} else {
|
||||
panic!(
|
||||
"INPUT: {:#x}, OUTPUT: PANIC!, EXPECTED: {:#x}",
|
||||
input.to_bits(),
|
||||
expected.to_bits()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! f32f32 {
|
||||
($lib:expr, $($fun:ident),+) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $fun() {
|
||||
let expected = include_bytes!(concat!("../bin/output/", $lib, ".", stringify!($fun)))
|
||||
.chunks_exact(4)
|
||||
.map(|chunk| {
|
||||
let mut buf = [0; 4];
|
||||
buf.copy_from_slice(chunk);
|
||||
f32::from_bits(u32::from_le(u32::from_le_bytes(buf)))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for ((i0, i1), expected) in $crate::F32F32.iter().zip(&expected) {
|
||||
if let Ok(output) = panic::catch_unwind(|| libm::$fun(*i0, *i1)) {
|
||||
if let Err(error) = libm::_eqf(output, *expected) {
|
||||
panic!(
|
||||
"INPUT: ({:#x}, {:#x}), OUTPUT: {:#x}, EXPECTED: {:#x}, ERROR: {}",
|
||||
i0.to_bits(),
|
||||
i1.to_bits(),
|
||||
output.to_bits(),
|
||||
expected.to_bits(),
|
||||
error
|
||||
);
|
||||
}
|
||||
} else {
|
||||
panic!(
|
||||
"INPUT: ({:#x}, {:#x}), OUTPUT: PANIC!, EXPECTED: {:#x}",
|
||||
i0.to_bits(),
|
||||
i1.to_bits(),
|
||||
expected.to_bits()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! f32f32f32 {
|
||||
($lib:expr, $($fun:ident),+) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $fun() {
|
||||
let expected = include_bytes!(concat!("../bin/output/", $lib, ".", stringify!($fun)))
|
||||
.chunks_exact(4)
|
||||
.map(|chunk| {
|
||||
let mut buf = [0; 4];
|
||||
buf.copy_from_slice(chunk);
|
||||
f32::from_bits(u32::from_le(u32::from_le_bytes(buf)))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for ((i0, i1, i2), expected) in $crate::F32F32F32.iter().zip(&expected) {
|
||||
if let Ok(output) = panic::catch_unwind(|| libm::$fun(*i0, *i1, *i2)) {
|
||||
if let Err(error) = libm::_eqf(output, *expected) {
|
||||
panic!(
|
||||
"INPUT: ({:#x}, {:#x}, {:#x}), OUTPUT: {:#x}, EXPECTED: {:#x}, ERROR: {}",
|
||||
i0.to_bits(),
|
||||
i1.to_bits(),
|
||||
i2.to_bits(),
|
||||
output.to_bits(),
|
||||
expected.to_bits(),
|
||||
error
|
||||
);
|
||||
}
|
||||
} else {
|
||||
panic!(
|
||||
"INPUT: ({:#x}, {:#x}), OUTPUT: PANIC!, EXPECTED: {:#x}",
|
||||
i0.to_bits(),
|
||||
i1.to_bits(),
|
||||
expected.to_bits()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! f32i32 {
|
||||
($lib:expr, $($fun:ident),+) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $fun() {
|
||||
let expected = include_bytes!(concat!("../bin/output/", $lib, ".", stringify!($fun)))
|
||||
.chunks_exact(4)
|
||||
.map(|chunk| {
|
||||
let mut buf = [0; 4];
|
||||
buf.copy_from_slice(chunk);
|
||||
f32::from_bits(u32::from_le(u32::from_le_bytes(buf)))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for ((i0, i1), expected) in $crate::F32I32.iter().zip(&expected) {
|
||||
if let Ok(output) = panic::catch_unwind(|| libm::$fun(*i0, *i1)) {
|
||||
if let Err(error) = libm::_eqf(output, *expected) {
|
||||
panic!(
|
||||
"INPUT: ({:#x}, {:#x}), OUTPUT: {:#x}, EXPECTED: {:#x}, ERROR: {}",
|
||||
i0.to_bits(),
|
||||
i1,
|
||||
output.to_bits(),
|
||||
expected.to_bits(),
|
||||
error
|
||||
);
|
||||
}
|
||||
} else {
|
||||
panic!(
|
||||
"INPUT: ({:#x}, {:#x}), OUTPUT: PANIC!, EXPECTED: {:#x}",
|
||||
i0.to_bits(),
|
||||
i1,
|
||||
expected.to_bits()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! f64 {
|
||||
($lib:expr, $($fun:ident),+) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $fun() {
|
||||
let expected = include_bytes!(concat!("../bin/output/", $lib, ".", stringify!($fun)))
|
||||
.chunks_exact(8)
|
||||
.map(|chunk| {
|
||||
let mut buf = [0; 8];
|
||||
buf.copy_from_slice(chunk);
|
||||
f64::from_bits(u64::from_le(u64::from_le_bytes(buf)))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (input, expected) in shared::F64.iter().zip(&expected) {
|
||||
if let Ok(output) = panic::catch_unwind(|| libm::$fun(*input)) {
|
||||
if let Err(error) = libm::_eq(output, *expected) {
|
||||
panic!(
|
||||
"INPUT: {:#x}, OUTPUT: {:#x}, EXPECTED: {:#x}, ERROR: {}",
|
||||
input.to_bits(),
|
||||
output.to_bits(),
|
||||
expected.to_bits(),
|
||||
error
|
||||
);
|
||||
}
|
||||
} else {
|
||||
panic!(
|
||||
"INPUT: {:#x}, OUTPUT: PANIC!, EXPECTED: {:#x}",
|
||||
input.to_bits(),
|
||||
expected.to_bits()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! f64f64 {
|
||||
($lib:expr, $($fun:ident),+) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $fun() {
|
||||
let expected = include_bytes!(concat!("../bin/output/", $lib, ".", stringify!($fun)))
|
||||
.chunks_exact(8)
|
||||
.map(|chunk| {
|
||||
let mut buf = [0; 8];
|
||||
buf.copy_from_slice(chunk);
|
||||
f64::from_bits(u64::from_le(u64::from_le_bytes(buf)))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for ((i0, i1), expected) in shared::F64F64.iter().zip(&expected) {
|
||||
if let Ok(output) = panic::catch_unwind(|| libm::$fun(*i0, *i1)) {
|
||||
if let Err(error) = libm::_eq(output, *expected) {
|
||||
panic!(
|
||||
"INPUT: ({:#x}, {:#x}), OUTPUT: {:#x}, EXPECTED: {:#x}, ERROR: {}",
|
||||
i0.to_bits(),
|
||||
i1.to_bits(),
|
||||
output.to_bits(),
|
||||
expected.to_bits(),
|
||||
error
|
||||
);
|
||||
}
|
||||
} else {
|
||||
panic!(
|
||||
"INPUT: ({:#x}, {:#x}), OUTPUT: PANIC!, EXPECTED: {:#x}",
|
||||
i0.to_bits(),
|
||||
i1.to_bits(),
|
||||
expected.to_bits()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! f64f64f64 {
|
||||
($lib:expr, $($fun:ident),+) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $fun() {
|
||||
let expected = include_bytes!(concat!("../bin/output/", $lib, ".", stringify!($fun)))
|
||||
.chunks_exact(8)
|
||||
.map(|chunk| {
|
||||
let mut buf = [0; 8];
|
||||
buf.copy_from_slice(chunk);
|
||||
f64::from_bits(u64::from_le(u64::from_le_bytes(buf)))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for ((i0, i1, i2), expected) in shared::F64F64F64.iter().zip(&expected) {
|
||||
if let Ok(output) = panic::catch_unwind(|| libm::$fun(*i0, *i1, *i2)) {
|
||||
if let Err(error) = libm::_eq(output, *expected) {
|
||||
panic!(
|
||||
"INPUT: ({:#x}, {:#x}, {:#x}), OUTPUT: {:#x}, EXPECTED: {:#x}, ERROR: {}",
|
||||
i0.to_bits(),
|
||||
i1.to_bits(),
|
||||
i2.to_bits(),
|
||||
output.to_bits(),
|
||||
expected.to_bits(),
|
||||
error
|
||||
);
|
||||
}
|
||||
} else {
|
||||
panic!(
|
||||
"INPUT: ({:#x}, {:#x}), OUTPUT: PANIC!, EXPECTED: {:#x}",
|
||||
i0.to_bits(),
|
||||
i1.to_bits(),
|
||||
expected.to_bits()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! f64i32 {
|
||||
($lib:expr, $($fun:ident),+) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $fun() {
|
||||
let expected = include_bytes!(concat!("../bin/output/", $lib, ".", stringify!($fun)))
|
||||
.chunks_exact(8)
|
||||
.map(|chunk| {
|
||||
let mut buf = [0; 8];
|
||||
buf.copy_from_slice(chunk);
|
||||
f64::from_bits(u64::from_le(u64::from_le_bytes(buf)))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for ((i0, i1), expected) in shared::F64I32.iter().zip(&expected) {
|
||||
if let Ok(output) = panic::catch_unwind(|| libm::$fun(*i0, *i1)) {
|
||||
if let Err(error) = libm::_eq(output, *expected) {
|
||||
panic!(
|
||||
"INPUT: ({:#x}, {:#x}), OUTPUT: {:#x}, EXPECTED: {:#x}, ERROR: {}",
|
||||
i0.to_bits(),
|
||||
i1,
|
||||
output.to_bits(),
|
||||
expected.to_bits(),
|
||||
error
|
||||
);
|
||||
}
|
||||
} else {
|
||||
panic!(
|
||||
"INPUT: ({:#x}, {:#x}), OUTPUT: PANIC!, EXPECTED: {:#x}",
|
||||
i0.to_bits(),
|
||||
i1,
|
||||
expected.to_bits()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
@ -625,3 +625,6 @@ mod private {
|
||||
impl Sealed for f32 {}
|
||||
impl Sealed for f64 {}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
include!(concat!(env!("OUT_DIR"), "/tests.rs"));
|
||||
|
Loading…
x
Reference in New Issue
Block a user