summaryrefslogtreecommitdiffstats
path: root/vendor/libm/build.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--vendor/libm/build.rs444
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());
+ }
+}