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::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 
generate()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
parse(s: &str) -> Function86     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 
generate_random_tests<R: Rng>(functions: &mut [Function], rng: &mut R)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 {
gen_i64<R: Rng>(&self, r: &mut R) -> i64170         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 
libc_ty(&self) -> &'static str207         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 
libc_pty(&self) -> &'static str216         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 
default(&self) -> &'static str225         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 
to_i64(&self) -> &'static str234         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 
generate_test_outputs(functions: &mut [Function])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.
generate_unit_tests(functions: &[Function])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 }
445