diff options
Diffstat (limited to '')
-rw-r--r-- | vendor/libm/build.rs | 444 |
1 files changed, 444 insertions, 0 deletions
diff --git a/vendor/libm/build.rs b/vendor/libm/build.rs new file mode 100644 index 000000000..9af6dec93 --- /dev/null +++ b/vendor/libm/build.rs @@ -0,0 +1,444 @@ +use std::env; + +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + + #[cfg(feature = "musl-reference-tests")] + musl_reference_tests::generate(); + + if !cfg!(feature = "checked") { + let lvl = env::var("OPT_LEVEL").unwrap(); + if lvl != "0" { + println!("cargo:rustc-cfg=assert_no_panic"); + } + } +} + +#[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] = &["fenv.rs"]; + + struct Function { + name: String, + args: Vec<Ty>, + ret: Vec<Ty>, + tests: Vec<Test>, + } + + enum Ty { + F32, + F64, + I32, + Bool, + } + + struct Test { + inputs: Vec<i64>, + outputs: Vec<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")); + while let Some(function_to_test) = functions.next() { + 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); + //panic!("Boo"); + // ... 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_retty(tail.replace("{", "").trim()); + + 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 parse_retty(s: &str) -> Vec<Ty> { + match s { + "(f32, f32)" => vec![Ty::F32, Ty::F32], + "(f32, i32)" => vec![Ty::F32, Ty::I32], + "(f64, f64)" => vec![Ty::F64, Ty::F64], + "(f64, i32)" => vec![Ty::F64, Ty::I32], + other => vec![parse_ty(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, rng)); + } + } + + fn generate_test<R: Rng>(function: &Function, rng: &mut R) -> Test { + let mut inputs = function + .args + .iter() + .map(|ty| ty.gen_i64(rng)) + .collect::<Vec<_>>(); + + // First argument to this function appears to be a number of + // iterations, so passing in massive random numbers causes it to + // take forever to execute, so make sure we're not running random + // math code until the heat death of the universe. + if function.name == "jn" || function.name == "jnf" { + inputs[0] &= 0xffff; + } + + Test { + inputs, + // zero output for now since we'll generate it later + outputs: vec![], + } + } + } + + impl Ty { + fn gen_i64<R: Rng>(&self, r: &mut R) -> i64 { + use std::f32; + use std::f64; + + return match self { + Ty::F32 => { + if r.gen_range(0, 20) < 1 { + let i = *[f32::NAN, f32::INFINITY, f32::NEG_INFINITY] + .choose(r) + .unwrap(); + i.to_bits().into() + } else { + r.gen::<f32>().to_bits().into() + } + } + Ty::F64 => { + if r.gen_range(0, 20) < 1 { + let i = *[f64::NAN, f64::INFINITY, f64::NEG_INFINITY] + .choose(r) + .unwrap(); + i.to_bits() as i64 + } else { + 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 libc_pty(&self) -> &'static str { + match self { + Ty::F32 => "*mut f32", + Ty::F64 => "*mut f64", + Ty::I32 => "*mut i32", + Ty::Bool => "*mut i32", + } + } + + fn default(&self) -> &'static str { + match self { + Ty::F32 => "0_f32", + Ty::F64 => "0_f64", + Ty::I32 => "0_i32", + Ty::Bool => "false", + } + } + + fn to_i64(&self) -> &'static str { + match self { + Ty::F32 => ".to_bits() as i64", + Ty::F64 => ".to_bits() as i64", + Ty::I32 => " as i64", + Ty::Bool => " as i64", + } + } + } + + 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("("); + + let (ret, retptr) = match function.name.as_str() { + "sincos" | "sincosf" => (None, &function.ret[..]), + _ => (Some(&function.ret[0]), &function.ret[1..]), + }; + for (i, arg) in function.args.iter().enumerate() { + src.push_str(&format!("arg{}: {},", i, arg.libc_ty())); + } + for (i, ret) in retptr.iter().enumerate() { + src.push_str(&format!("argret{}: {},", i, ret.libc_pty())); + } + src.push_str(")"); + if let Some(ty) = ret { + src.push_str(" -> "); + src.push_str(ty.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 {"); + for (i, arg) in retptr.iter().enumerate() { + src.push_str(&format!("let mut argret{} = {};", i, arg.default())); + } + 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(","); + } + for (i, _) in retptr.iter().enumerate() { + src.push_str(&format!("&mut argret{},", i)); + } + src.push_str(");"); + if let Some(ty) = &ret { + src.push_str(&format!("let output = output{};", ty.to_i64())); + src.push_str("result.extend_from_slice(&output.to_le_bytes());"); + } + + for (i, ret) in retptr.iter().enumerate() { + src.push_str(&format!( + "result.extend_from_slice(&(argret{}{}).to_le_bytes());", + i, + ret.to_i64(), + )); + } + 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 f in functions.iter_mut() { + for test in f.tests.iter_mut() { + test.outputs = (0..f.ret.len()).map(|_| results.next().unwrap()).collect(); + } + } + 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(), + function.ret.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("["); + for val in test.outputs.iter() { + src.push_str(&val.to_string()); + src.push_str(","); + } + src.push_str("],"); + 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(");"); + + for (i, ret) in function.ret.iter().enumerate() { + let get = if function.ret.len() == 1 { + String::new() + } else { + format!(".{}", i) + }; + src.push_str(&(match ret { + Ty::F32 => format!("if _eqf(output{}, f32::from_bits(expected[{}] as u32)).is_ok() {{ continue }}", get, i), + Ty::F64 => format!("if _eq(output{}, f64::from_bits(expected[{}] as u64)).is_ok() {{ continue }}", get, i), + Ty::I32 => format!("if output{} as i64 == expected[{}] {{ continue }}", get, i), + Ty::Bool => unreachable!(), + })); + } + + src.push_str( + r#" + panic!("INPUT: {:?} EXPECTED: {:?} ACTUAL {:?}", test, expected, output); + "#, + ); + src.push_str("}"); + + src.push_str("}"); + } + + let path = format!("{}/musl-tests.rs", dst); + fs::write(&path, src).unwrap(); + + // Try to make it somewhat pretty + drop(Command::new("rustfmt").arg(&path).status()); + } +} |