blob: 9af6dec938b17a9b1104bb15df4f950e65cd342d [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;
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}