mirror of
				https://github.com/rust-lang/rust.git
				synced 2025-10-31 04:57:19 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			436 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			436 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| fn main() {
 | |
|     #[cfg(feature = "test-musl-serialized")]
 | |
|     musl_reference_tests::generate();
 | |
| }
 | |
| 
 | |
| #[cfg(feature = "test-musl-serialized")]
 | |
| mod musl_reference_tests {
 | |
|     use std::path::PathBuf;
 | |
|     use std::process::Command;
 | |
|     use std::{env, fs};
 | |
| 
 | |
|     use rand::Rng;
 | |
|     use rand::seq::SliceRandom;
 | |
| 
 | |
|     // 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",
 | |
|         // These are giving slightly different results compared to musl
 | |
|         "lgamma.rs",
 | |
|         "lgammaf.rs",
 | |
|         "tgamma.rs",
 | |
|         "j0.rs",
 | |
|         "j0f.rs",
 | |
|         "jn.rs",
 | |
|         "jnf.rs",
 | |
|         "j1.rs",
 | |
|         "j1f.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() {
 | |
|         // PowerPC tests are failing on LLVM 13: https://github.com/rust-lang/rust/issues/88520
 | |
|         let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
 | |
|         let libm_test = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
 | |
|         let math_src = libm_test.join("../../src/math");
 | |
| 
 | |
|         if target_arch == "powerpc64" {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         let files = fs::read_dir(math_src).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, 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 = libm::");
 | |
|             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 libm::_eqf(output{}, f32::from_bits(expected[{}] as u32)).is_ok() {{ continue }}", get, i),
 | |
|                     Ty::F64 => format!("if libm::_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());
 | |
|     }
 | |
| }
 | 
