Jeff Vander Stoep | fb178dc | 2020-12-04 13:55:39 +0100 | [diff] [blame] | 1 | use std::env; |
| 2 | |
| 3 | fn main() { |
| 4 | println!("cargo:rerun-if-changed=build.rs"); |
| 5 | |
| 6 | #[cfg(feature = "musl-reference-tests")] |
| 7 | musl_reference_tests::generate(); |
| 8 | |
| 9 | if !cfg!(feature = "checked") { |
| 10 | let lvl = env::var("OPT_LEVEL").unwrap(); |
| 11 | if lvl != "0" { |
| 12 | println!("cargo:rustc-cfg=assert_no_panic"); |
| 13 | } |
| 14 | } |
| 15 | } |
| 16 | |
| 17 | #[cfg(feature = "musl-reference-tests")] |
| 18 | mod musl_reference_tests { |
| 19 | use rand::seq::SliceRandom; |
| 20 | use rand::Rng; |
| 21 | use std::fs; |
| 22 | use std::process::Command; |
| 23 | |
| 24 | // Number of tests to generate for each function |
| 25 | const NTESTS: usize = 500; |
| 26 | |
| 27 | // These files are all internal functions or otherwise miscellaneous, not |
| 28 | // defining a function we want to test. |
| 29 | const IGNORED_FILES: &[&str] = &["fenv.rs"]; |
| 30 | |
| 31 | struct Function { |
| 32 | name: String, |
| 33 | args: Vec<Ty>, |
| 34 | ret: Vec<Ty>, |
| 35 | tests: Vec<Test>, |
| 36 | } |
| 37 | |
| 38 | enum Ty { |
| 39 | F32, |
| 40 | F64, |
| 41 | I32, |
| 42 | Bool, |
| 43 | } |
| 44 | |
| 45 | struct Test { |
| 46 | inputs: Vec<i64>, |
| 47 | outputs: Vec<i64>, |
| 48 | } |
| 49 | |
| 50 | pub fn generate() { |
| 51 | let files = fs::read_dir("src/math") |
| 52 | .unwrap() |
| 53 | .map(|f| f.unwrap().path()) |
| 54 | .collect::<Vec<_>>(); |
| 55 | |
| 56 | let mut math = Vec::new(); |
| 57 | for file in files { |
| 58 | if IGNORED_FILES.iter().any(|f| file.ends_with(f)) { |
| 59 | continue; |
| 60 | } |
| 61 | |
| 62 | println!("generating musl reference tests in {:?}", file); |
| 63 | |
| 64 | let contents = fs::read_to_string(file).unwrap(); |
| 65 | let mut functions = contents.lines().filter(|f| f.starts_with("pub fn")); |
| 66 | while let Some(function_to_test) = functions.next() { |
| 67 | math.push(parse(function_to_test)); |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | // Generate a bunch of random inputs for each function. This will |
| 72 | // attempt to generate a good set of uniform test cases for exercising |
| 73 | // all the various functionality. |
| 74 | generate_random_tests(&mut math, &mut rand::thread_rng()); |
| 75 | |
| 76 | // After we have all our inputs, use the x86_64-unknown-linux-musl |
| 77 | // target to generate the expected output. |
| 78 | generate_test_outputs(&mut math); |
| 79 | //panic!("Boo"); |
| 80 | // ... and now that we have both inputs and expected outputs, do a bunch |
| 81 | // of codegen to create the unit tests which we'll actually execute. |
| 82 | generate_unit_tests(&math); |
| 83 | } |
| 84 | |
| 85 | /// A "poor man's" parser for the signature of a function |
| 86 | fn parse(s: &str) -> Function { |
| 87 | let s = eat(s, "pub fn "); |
| 88 | let pos = s.find('(').unwrap(); |
| 89 | let name = &s[..pos]; |
| 90 | let s = &s[pos + 1..]; |
| 91 | let end = s.find(')').unwrap(); |
| 92 | let args = s[..end] |
| 93 | .split(',') |
| 94 | .map(|arg| { |
| 95 | let colon = arg.find(':').unwrap(); |
| 96 | parse_ty(arg[colon + 1..].trim()) |
| 97 | }) |
| 98 | .collect::<Vec<_>>(); |
| 99 | let tail = &s[end + 1..]; |
| 100 | let tail = eat(tail, " -> "); |
| 101 | let ret = parse_retty(tail.replace("{", "").trim()); |
| 102 | |
| 103 | return Function { |
| 104 | name: name.to_string(), |
| 105 | args, |
| 106 | ret, |
| 107 | tests: Vec::new(), |
| 108 | }; |
| 109 | |
| 110 | fn parse_ty(s: &str) -> Ty { |
| 111 | match s { |
| 112 | "f32" => Ty::F32, |
| 113 | "f64" => Ty::F64, |
| 114 | "i32" => Ty::I32, |
| 115 | "bool" => Ty::Bool, |
| 116 | other => panic!("unknown type `{}`", other), |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | fn parse_retty(s: &str) -> Vec<Ty> { |
| 121 | match s { |
| 122 | "(f32, f32)" => vec![Ty::F32, Ty::F32], |
| 123 | "(f32, i32)" => vec![Ty::F32, Ty::I32], |
| 124 | "(f64, f64)" => vec![Ty::F64, Ty::F64], |
| 125 | "(f64, i32)" => vec![Ty::F64, Ty::I32], |
| 126 | other => vec![parse_ty(other)], |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | fn eat<'a>(s: &'a str, prefix: &str) -> &'a str { |
| 131 | if s.starts_with(prefix) { |
| 132 | &s[prefix.len()..] |
| 133 | } else { |
| 134 | panic!("{:?} didn't start with {:?}", s, prefix) |
| 135 | } |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | fn generate_random_tests<R: Rng>(functions: &mut [Function], rng: &mut R) { |
| 140 | for function in functions { |
| 141 | for _ in 0..NTESTS { |
| 142 | function.tests.push(generate_test(function, rng)); |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | fn generate_test<R: Rng>(function: &Function, rng: &mut R) -> Test { |
| 147 | let mut inputs = function |
| 148 | .args |
| 149 | .iter() |
| 150 | .map(|ty| ty.gen_i64(rng)) |
| 151 | .collect::<Vec<_>>(); |
| 152 | |
| 153 | // First argument to this function appears to be a number of |
| 154 | // iterations, so passing in massive random numbers causes it to |
| 155 | // take forever to execute, so make sure we're not running random |
| 156 | // math code until the heat death of the universe. |
| 157 | if function.name == "jn" || function.name == "jnf" { |
| 158 | inputs[0] &= 0xffff; |
| 159 | } |
| 160 | |
| 161 | Test { |
| 162 | inputs, |
| 163 | // zero output for now since we'll generate it later |
| 164 | outputs: vec![], |
| 165 | } |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | impl Ty { |
| 170 | fn gen_i64<R: Rng>(&self, r: &mut R) -> i64 { |
| 171 | use std::f32; |
| 172 | use std::f64; |
| 173 | |
| 174 | return match self { |
| 175 | Ty::F32 => { |
| 176 | if r.gen_range(0, 20) < 1 { |
| 177 | let i = *[f32::NAN, f32::INFINITY, f32::NEG_INFINITY] |
| 178 | .choose(r) |
| 179 | .unwrap(); |
| 180 | i.to_bits().into() |
| 181 | } else { |
| 182 | r.gen::<f32>().to_bits().into() |
| 183 | } |
| 184 | } |
| 185 | Ty::F64 => { |
| 186 | if r.gen_range(0, 20) < 1 { |
| 187 | let i = *[f64::NAN, f64::INFINITY, f64::NEG_INFINITY] |
| 188 | .choose(r) |
| 189 | .unwrap(); |
| 190 | i.to_bits() as i64 |
| 191 | } else { |
| 192 | r.gen::<f64>().to_bits() as i64 |
| 193 | } |
| 194 | } |
| 195 | Ty::I32 => { |
| 196 | if r.gen_range(0, 10) < 1 { |
| 197 | let i = *[i32::max_value(), 0, i32::min_value()].choose(r).unwrap(); |
| 198 | i.into() |
| 199 | } else { |
| 200 | r.gen::<i32>().into() |
| 201 | } |
| 202 | } |
| 203 | Ty::Bool => r.gen::<bool>() as i64, |
| 204 | }; |
| 205 | } |
| 206 | |
| 207 | fn libc_ty(&self) -> &'static str { |
| 208 | match self { |
| 209 | Ty::F32 => "f32", |
| 210 | Ty::F64 => "f64", |
| 211 | Ty::I32 => "i32", |
| 212 | Ty::Bool => "i32", |
| 213 | } |
| 214 | } |
| 215 | |
| 216 | fn libc_pty(&self) -> &'static str { |
| 217 | match self { |
| 218 | Ty::F32 => "*mut f32", |
| 219 | Ty::F64 => "*mut f64", |
| 220 | Ty::I32 => "*mut i32", |
| 221 | Ty::Bool => "*mut i32", |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | fn default(&self) -> &'static str { |
| 226 | match self { |
| 227 | Ty::F32 => "0_f32", |
| 228 | Ty::F64 => "0_f64", |
| 229 | Ty::I32 => "0_i32", |
| 230 | Ty::Bool => "false", |
| 231 | } |
| 232 | } |
| 233 | |
| 234 | fn to_i64(&self) -> &'static str { |
| 235 | match self { |
| 236 | Ty::F32 => ".to_bits() as i64", |
| 237 | Ty::F64 => ".to_bits() as i64", |
| 238 | Ty::I32 => " as i64", |
| 239 | Ty::Bool => " as i64", |
| 240 | } |
| 241 | } |
| 242 | } |
| 243 | |
| 244 | fn generate_test_outputs(functions: &mut [Function]) { |
| 245 | let mut src = String::new(); |
| 246 | let dst = std::env::var("OUT_DIR").unwrap(); |
| 247 | |
| 248 | // Generate a program which will run all tests with all inputs in |
| 249 | // `functions`. This program will write all outputs to stdout (in a |
| 250 | // binary format). |
| 251 | src.push_str("use std::io::Write;"); |
| 252 | src.push_str("fn main() {"); |
| 253 | src.push_str("let mut result = Vec::new();"); |
| 254 | for function in functions.iter_mut() { |
| 255 | src.push_str("unsafe {"); |
| 256 | src.push_str("extern { fn "); |
| 257 | src.push_str(&function.name); |
| 258 | src.push_str("("); |
| 259 | |
| 260 | let (ret, retptr) = match function.name.as_str() { |
| 261 | "sincos" | "sincosf" => (None, &function.ret[..]), |
| 262 | _ => (Some(&function.ret[0]), &function.ret[1..]), |
| 263 | }; |
| 264 | for (i, arg) in function.args.iter().enumerate() { |
| 265 | src.push_str(&format!("arg{}: {},", i, arg.libc_ty())); |
| 266 | } |
| 267 | for (i, ret) in retptr.iter().enumerate() { |
| 268 | src.push_str(&format!("argret{}: {},", i, ret.libc_pty())); |
| 269 | } |
| 270 | src.push_str(")"); |
| 271 | if let Some(ty) = ret { |
| 272 | src.push_str(" -> "); |
| 273 | src.push_str(ty.libc_ty()); |
| 274 | } |
| 275 | src.push_str("; }"); |
| 276 | |
| 277 | src.push_str(&format!("static TESTS: &[[i64; {}]]", function.args.len())); |
| 278 | src.push_str(" = &["); |
| 279 | for test in function.tests.iter() { |
| 280 | src.push_str("["); |
| 281 | for val in test.inputs.iter() { |
| 282 | src.push_str(&val.to_string()); |
| 283 | src.push_str(","); |
| 284 | } |
| 285 | src.push_str("],"); |
| 286 | } |
| 287 | src.push_str("];"); |
| 288 | |
| 289 | src.push_str("for test in TESTS {"); |
| 290 | for (i, arg) in retptr.iter().enumerate() { |
| 291 | src.push_str(&format!("let mut argret{} = {};", i, arg.default())); |
| 292 | } |
| 293 | src.push_str("let output = "); |
| 294 | src.push_str(&function.name); |
| 295 | src.push_str("("); |
| 296 | for (i, arg) in function.args.iter().enumerate() { |
| 297 | src.push_str(&match arg { |
| 298 | Ty::F32 => format!("f32::from_bits(test[{}] as u32)", i), |
| 299 | Ty::F64 => format!("f64::from_bits(test[{}] as u64)", i), |
| 300 | Ty::I32 => format!("test[{}] as i32", i), |
| 301 | Ty::Bool => format!("test[{}] as i32", i), |
| 302 | }); |
| 303 | src.push_str(","); |
| 304 | } |
| 305 | for (i, _) in retptr.iter().enumerate() { |
| 306 | src.push_str(&format!("&mut argret{},", i)); |
| 307 | } |
| 308 | src.push_str(");"); |
| 309 | if let Some(ty) = &ret { |
| 310 | src.push_str(&format!("let output = output{};", ty.to_i64())); |
| 311 | src.push_str("result.extend_from_slice(&output.to_le_bytes());"); |
| 312 | } |
| 313 | |
| 314 | for (i, ret) in retptr.iter().enumerate() { |
| 315 | src.push_str(&format!( |
| 316 | "result.extend_from_slice(&(argret{}{}).to_le_bytes());", |
| 317 | i, |
| 318 | ret.to_i64(), |
| 319 | )); |
| 320 | } |
| 321 | src.push_str("}"); |
| 322 | |
| 323 | src.push_str("}"); |
| 324 | } |
| 325 | |
| 326 | src.push_str("std::io::stdout().write_all(&result).unwrap();"); |
| 327 | |
| 328 | src.push_str("}"); |
| 329 | |
| 330 | let path = format!("{}/gen.rs", dst); |
| 331 | fs::write(&path, src).unwrap(); |
| 332 | |
| 333 | // Make it somewhat pretty if something goes wrong |
| 334 | drop(Command::new("rustfmt").arg(&path).status()); |
| 335 | |
| 336 | // Compile and execute this tests for the musl target, assuming we're an |
| 337 | // x86_64 host effectively. |
| 338 | let status = Command::new("rustc") |
| 339 | .current_dir(&dst) |
| 340 | .arg(&path) |
| 341 | .arg("--target=x86_64-unknown-linux-musl") |
| 342 | .status() |
| 343 | .unwrap(); |
| 344 | assert!(status.success()); |
| 345 | let output = Command::new("./gen").current_dir(&dst).output().unwrap(); |
| 346 | assert!(output.status.success()); |
| 347 | assert!(output.stderr.is_empty()); |
| 348 | |
| 349 | // Map all the output bytes back to an `i64` and then shove it all into |
| 350 | // the expected results. |
| 351 | let mut results = output.stdout.chunks_exact(8).map(|buf| { |
| 352 | let mut exact = [0; 8]; |
| 353 | exact.copy_from_slice(buf); |
| 354 | i64::from_le_bytes(exact) |
| 355 | }); |
| 356 | |
| 357 | for f in functions.iter_mut() { |
| 358 | for test in f.tests.iter_mut() { |
| 359 | test.outputs = (0..f.ret.len()).map(|_| results.next().unwrap()).collect(); |
| 360 | } |
| 361 | } |
| 362 | assert!(results.next().is_none()); |
| 363 | } |
| 364 | |
| 365 | /// Codegens a file which has a ton of `#[test]` annotations for all the |
| 366 | /// tests that we generated above. |
| 367 | fn generate_unit_tests(functions: &[Function]) { |
| 368 | let mut src = String::new(); |
| 369 | let dst = std::env::var("OUT_DIR").unwrap(); |
| 370 | |
| 371 | for function in functions { |
| 372 | src.push_str("#[test]"); |
| 373 | src.push_str("fn "); |
| 374 | src.push_str(&function.name); |
| 375 | src.push_str("_matches_musl() {"); |
| 376 | src.push_str(&format!( |
| 377 | "static TESTS: &[([i64; {}], [i64; {}])]", |
| 378 | function.args.len(), |
| 379 | function.ret.len(), |
| 380 | )); |
| 381 | src.push_str(" = &["); |
| 382 | for test in function.tests.iter() { |
| 383 | src.push_str("(["); |
| 384 | for val in test.inputs.iter() { |
| 385 | src.push_str(&val.to_string()); |
| 386 | src.push_str(","); |
| 387 | } |
| 388 | src.push_str("],"); |
| 389 | src.push_str("["); |
| 390 | for val in test.outputs.iter() { |
| 391 | src.push_str(&val.to_string()); |
| 392 | src.push_str(","); |
| 393 | } |
| 394 | src.push_str("],"); |
| 395 | src.push_str("),"); |
| 396 | } |
| 397 | src.push_str("];"); |
| 398 | |
| 399 | src.push_str("for (test, expected) in TESTS {"); |
| 400 | src.push_str("let output = "); |
| 401 | src.push_str(&function.name); |
| 402 | src.push_str("("); |
| 403 | for (i, arg) in function.args.iter().enumerate() { |
| 404 | src.push_str(&match arg { |
| 405 | Ty::F32 => format!("f32::from_bits(test[{}] as u32)", i), |
| 406 | Ty::F64 => format!("f64::from_bits(test[{}] as u64)", i), |
| 407 | Ty::I32 => format!("test[{}] as i32", i), |
| 408 | Ty::Bool => format!("test[{}] as i32", i), |
| 409 | }); |
| 410 | src.push_str(","); |
| 411 | } |
| 412 | src.push_str(");"); |
| 413 | |
| 414 | for (i, ret) in function.ret.iter().enumerate() { |
| 415 | let get = if function.ret.len() == 1 { |
| 416 | String::new() |
| 417 | } else { |
| 418 | format!(".{}", i) |
| 419 | }; |
| 420 | src.push_str(&(match ret { |
| 421 | Ty::F32 => format!("if _eqf(output{}, f32::from_bits(expected[{}] as u32)).is_ok() {{ continue }}", get, i), |
| 422 | Ty::F64 => format!("if _eq(output{}, f64::from_bits(expected[{}] as u64)).is_ok() {{ continue }}", get, i), |
| 423 | Ty::I32 => format!("if output{} as i64 == expected[{}] {{ continue }}", get, i), |
| 424 | Ty::Bool => unreachable!(), |
| 425 | })); |
| 426 | } |
| 427 | |
| 428 | src.push_str( |
| 429 | r#" |
| 430 | panic!("INPUT: {:?} EXPECTED: {:?} ACTUAL {:?}", test, expected, output); |
| 431 | "#, |
| 432 | ); |
| 433 | src.push_str("}"); |
| 434 | |
| 435 | src.push_str("}"); |
| 436 | } |
| 437 | |
| 438 | let path = format!("{}/musl-tests.rs", dst); |
| 439 | fs::write(&path, src).unwrap(); |
| 440 | |
| 441 | // Try to make it somewhat pretty |
| 442 | drop(Command::new("rustfmt").arg(&path).status()); |
| 443 | } |
| 444 | } |