blob: 80145a9ccac97fb432a95aabeb8e83a047ab6744 [file] [log] [blame]
Jeff Vander Stoepfb178dc2020-12-04 13:55:39 +01001use std::env;
2
3fn 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")]
18mod musl_reference_tests {
19 use rand::seq::SliceRandom;
20 use rand::Rng;
Jeff Vander Stoepb0acf3c2022-12-12 14:12:46 +010021 use std::env;
Jeff Vander Stoepfb178dc2020-12-04 13:55:39 +010022 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.
Jeff Vander Stoepb0acf3c2022-12-12 14:12:46 +010030 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 ];
Jeff Vander Stoepfb178dc2020-12-04 13:55:39 +010043
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
63 pub fn generate() {
Jeff Vander Stoepb0acf3c2022-12-12 14:12:46 +010064 // 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
Jeff Vander Stoepfb178dc2020-12-04 13:55:39 +010070 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
105 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
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 {
189 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
226 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
235 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
244 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
253 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
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.
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}