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