]>
Commit | Line | Data |
---|---|---|
3c0e092e | 1 | #![feature(slice_partition_dedup)] |
c295e0f8 XL |
2 | #[macro_use] |
3 | extern crate lazy_static; | |
4 | #[macro_use] | |
5 | extern crate log; | |
6 | ||
7 | use std::fs::File; | |
8 | use std::io::Write; | |
9 | use std::process::Command; | |
10 | ||
11 | use clap::{App, Arg}; | |
12 | use intrinsic::Intrinsic; | |
3c0e092e | 13 | use itertools::Itertools; |
c295e0f8 XL |
14 | use rayon::prelude::*; |
15 | use types::TypeKind; | |
16 | ||
353b0b11 | 17 | use crate::acle_csv_parser::{get_acle_intrinsics, CsvMetadata}; |
3c0e092e XL |
18 | use crate::argument::Argument; |
19 | ||
20 | mod acle_csv_parser; | |
c295e0f8 XL |
21 | mod argument; |
22 | mod intrinsic; | |
23 | mod types; | |
24 | mod values; | |
25 | ||
f2b60f7d FG |
26 | // The number of times each intrinsic will be called. |
27 | const PASSES: u32 = 20; | |
28 | ||
c295e0f8 XL |
29 | #[derive(Debug, PartialEq)] |
30 | pub enum Language { | |
31 | Rust, | |
32 | C, | |
33 | } | |
34 | ||
f2b60f7d FG |
35 | fn gen_code_c( |
36 | intrinsic: &Intrinsic, | |
37 | constraints: &[&Argument], | |
38 | name: String, | |
39 | p64_armv7_workaround: bool, | |
40 | ) -> String { | |
3c0e092e XL |
41 | if let Some((current, constraints)) = constraints.split_last() { |
42 | let range = current | |
43 | .constraints | |
44 | .iter() | |
45 | .map(|c| c.to_range()) | |
46 | .flat_map(|r| r.into_iter()); | |
47 | ||
48 | range | |
49 | .map(|i| { | |
50 | format!( | |
51 | r#" {{ | |
52 | {ty} {name} = {val}; | |
53 | {pass} | |
54 | }}"#, | |
55 | name = current.name, | |
56 | ty = current.ty.c_type(), | |
57 | val = i, | |
f2b60f7d FG |
58 | pass = gen_code_c( |
59 | intrinsic, | |
60 | constraints, | |
9ffffee4 | 61 | format!("{name}-{i}"), |
f2b60f7d FG |
62 | p64_armv7_workaround |
63 | ) | |
3c0e092e XL |
64 | ) |
65 | }) | |
66 | .collect() | |
67 | } else { | |
f2b60f7d | 68 | intrinsic.generate_loop_c(&name, PASSES, p64_armv7_workaround) |
3c0e092e XL |
69 | } |
70 | } | |
71 | ||
f2b60f7d | 72 | fn generate_c_program( |
353b0b11 | 73 | notices: &str, |
f2b60f7d FG |
74 | header_files: &[&str], |
75 | intrinsic: &Intrinsic, | |
76 | p64_armv7_workaround: bool, | |
77 | ) -> String { | |
3c0e092e XL |
78 | let constraints = intrinsic |
79 | .arguments | |
80 | .iter() | |
81 | .filter(|i| i.has_constraint()) | |
82 | .collect_vec(); | |
83 | ||
c295e0f8 | 84 | format!( |
353b0b11 | 85 | r#"{notices}{header_files} |
c295e0f8 XL |
86 | #include <iostream> |
87 | #include <cstring> | |
88 | #include <iomanip> | |
89 | #include <sstream> | |
a2a8927a | 90 | |
c295e0f8 XL |
91 | template<typename T1, typename T2> T1 cast(T2 x) {{ |
92 | static_assert(sizeof(T1) == sizeof(T2), "sizeof T1 and T2 must be the same"); | |
f2b60f7d | 93 | T1 ret{{}}; |
c295e0f8 XL |
94 | memcpy(&ret, &x, sizeof(T1)); |
95 | return ret; | |
96 | }} | |
a2a8927a XL |
97 | |
98 | #ifdef __aarch64__ | |
c295e0f8 XL |
99 | std::ostream& operator<<(std::ostream& os, poly128_t value) {{ |
100 | std::stringstream temp; | |
101 | do {{ | |
102 | int n = value % 10; | |
103 | value /= 10; | |
104 | temp << n; | |
105 | }} while (value != 0); | |
106 | std::string tempstr(temp.str()); | |
107 | std::string res(tempstr.rbegin(), tempstr.rend()); | |
108 | os << res; | |
109 | return os; | |
110 | }} | |
a2a8927a XL |
111 | #endif |
112 | ||
f2b60f7d FG |
113 | {arglists} |
114 | ||
c295e0f8 XL |
115 | int main(int argc, char **argv) {{ |
116 | {passes} | |
117 | return 0; | |
118 | }}"#, | |
119 | header_files = header_files | |
120 | .iter() | |
9ffffee4 | 121 | .map(|header| format!("#include <{header}>")) |
c295e0f8 XL |
122 | .collect::<Vec<_>>() |
123 | .join("\n"), | |
f2b60f7d FG |
124 | arglists = intrinsic.arguments.gen_arglists_c(PASSES), |
125 | passes = gen_code_c( | |
126 | intrinsic, | |
127 | constraints.as_slice(), | |
128 | Default::default(), | |
129 | p64_armv7_workaround | |
130 | ), | |
c295e0f8 XL |
131 | ) |
132 | } | |
133 | ||
3c0e092e XL |
134 | fn gen_code_rust(intrinsic: &Intrinsic, constraints: &[&Argument], name: String) -> String { |
135 | if let Some((current, constraints)) = constraints.split_last() { | |
136 | let range = current | |
137 | .constraints | |
138 | .iter() | |
139 | .map(|c| c.to_range()) | |
140 | .flat_map(|r| r.into_iter()); | |
141 | ||
142 | range | |
143 | .map(|i| { | |
144 | format!( | |
145 | r#" {{ | |
146 | const {name}: {ty} = {val}; | |
147 | {pass} | |
148 | }}"#, | |
149 | name = current.name, | |
150 | ty = current.ty.rust_type(), | |
151 | val = i, | |
9ffffee4 | 152 | pass = gen_code_rust(intrinsic, constraints, format!("{name}-{i}")) |
3c0e092e XL |
153 | ) |
154 | }) | |
155 | .collect() | |
156 | } else { | |
f2b60f7d | 157 | intrinsic.generate_loop_rust(&name, PASSES) |
3c0e092e XL |
158 | } |
159 | } | |
160 | ||
353b0b11 | 161 | fn generate_rust_program(notices: &str, intrinsic: &Intrinsic, a32: bool) -> String { |
3c0e092e XL |
162 | let constraints = intrinsic |
163 | .arguments | |
164 | .iter() | |
165 | .filter(|i| i.has_constraint()) | |
166 | .collect_vec(); | |
167 | ||
c295e0f8 | 168 | format!( |
353b0b11 | 169 | r#"{notices}#![feature(simd_ffi)] |
c295e0f8 XL |
170 | #![feature(link_llvm_intrinsics)] |
171 | #![feature(stdsimd)] | |
172 | #![allow(overflowing_literals)] | |
3c0e092e | 173 | #![allow(non_upper_case_globals)] |
a2a8927a | 174 | use core_arch::arch::{target_arch}::*; |
c295e0f8 | 175 | |
f2b60f7d FG |
176 | {arglists} |
177 | ||
c295e0f8 XL |
178 | fn main() {{ |
179 | {passes} | |
180 | }} | |
181 | "#, | |
a2a8927a | 182 | target_arch = if a32 { "arm" } else { "aarch64" }, |
f2b60f7d | 183 | arglists = intrinsic.arguments.gen_arglists_rust(PASSES), |
3c0e092e | 184 | passes = gen_code_rust(intrinsic, &constraints, Default::default()) |
c295e0f8 XL |
185 | ) |
186 | } | |
187 | ||
a2a8927a | 188 | fn compile_c(c_filename: &str, intrinsic: &Intrinsic, compiler: &str, a32: bool) -> bool { |
c295e0f8 XL |
189 | let flags = std::env::var("CPPFLAGS").unwrap_or("".into()); |
190 | ||
191 | let output = Command::new("sh") | |
192 | .arg("-c") | |
193 | .arg(format!( | |
194 | "{cpp} {cppflags} {arch_flags} -Wno-narrowing -O2 -target {target} -o c_programs/{intrinsic} {filename}", | |
a2a8927a XL |
195 | target = if a32 { "armv7-unknown-linux-gnueabihf" } else { "aarch64-unknown-linux-gnu" }, |
196 | arch_flags = if a32 { "-march=armv8.6-a+crypto+crc+dotprod" } else { "-march=armv8.6-a+crypto+sha3+crc+dotprod" }, | |
c295e0f8 XL |
197 | filename = c_filename, |
198 | intrinsic = intrinsic.name, | |
199 | cpp = compiler, | |
200 | cppflags = flags, | |
201 | )) | |
202 | .output(); | |
203 | if let Ok(output) = output { | |
204 | if output.status.success() { | |
205 | true | |
206 | } else { | |
a2a8927a XL |
207 | error!( |
208 | "Failed to compile code for intrinsic: {}\n\nstdout:\n{}\n\nstderr:\n{}", | |
209 | intrinsic.name, | |
210 | std::str::from_utf8(&output.stdout).unwrap_or(""), | |
211 | std::str::from_utf8(&output.stderr).unwrap_or("") | |
212 | ); | |
213 | false | |
c295e0f8 XL |
214 | } |
215 | } else { | |
216 | error!("Command failed: {:#?}", output); | |
217 | false | |
218 | } | |
219 | } | |
220 | ||
353b0b11 FG |
221 | fn build_notices(csv_metadata: &CsvMetadata, line_prefix: &str) -> String { |
222 | let mut notices = format!( | |
223 | "\ | |
224 | {line_prefix}This is a transient test file, not intended for distribution. Some aspects of the | |
225 | {line_prefix}test are derived from a CSV specification, published with the following notices: | |
226 | {line_prefix} | |
227 | " | |
228 | ); | |
229 | let lines = csv_metadata | |
230 | .notices_lines() | |
231 | .map(|line| format!("{line_prefix} {line}\n")); | |
232 | notices.extend(lines); | |
233 | notices.push_str("\n"); | |
234 | notices | |
235 | } | |
236 | ||
237 | fn build_c(notices: &str, intrinsics: &Vec<Intrinsic>, compiler: &str, a32: bool) -> bool { | |
c295e0f8 XL |
238 | let _ = std::fs::create_dir("c_programs"); |
239 | intrinsics | |
240 | .par_iter() | |
241 | .map(|i| { | |
242 | let c_filename = format!(r#"c_programs/{}.cpp"#, i.name); | |
243 | let mut file = File::create(&c_filename).unwrap(); | |
244 | ||
353b0b11 | 245 | let c_code = generate_c_program(notices, &["arm_neon.h", "arm_acle.h"], &i, a32); |
c295e0f8 | 246 | file.write_all(c_code.into_bytes().as_slice()).unwrap(); |
a2a8927a | 247 | compile_c(&c_filename, &i, compiler, a32) |
c295e0f8 XL |
248 | }) |
249 | .find_any(|x| !x) | |
250 | .is_none() | |
251 | } | |
252 | ||
353b0b11 FG |
253 | fn build_rust( |
254 | notices: &str, | |
255 | spdx_lic: &str, | |
256 | intrinsics: &Vec<Intrinsic>, | |
257 | toolchain: &str, | |
258 | a32: bool, | |
259 | ) -> bool { | |
c295e0f8 XL |
260 | intrinsics.iter().for_each(|i| { |
261 | let rust_dir = format!(r#"rust_programs/{}"#, i.name); | |
262 | let _ = std::fs::create_dir_all(&rust_dir); | |
9ffffee4 | 263 | let rust_filename = format!(r#"{rust_dir}/main.rs"#); |
c295e0f8 XL |
264 | let mut file = File::create(&rust_filename).unwrap(); |
265 | ||
353b0b11 | 266 | let c_code = generate_rust_program(notices, &i, a32); |
c295e0f8 XL |
267 | file.write_all(c_code.into_bytes().as_slice()).unwrap(); |
268 | }); | |
269 | ||
270 | let mut cargo = File::create("rust_programs/Cargo.toml").unwrap(); | |
271 | cargo | |
272 | .write_all( | |
273 | format!( | |
274 | r#"[package] | |
353b0b11 | 275 | name = "intrinsic-test-programs" |
c295e0f8 XL |
276 | version = "{version}" |
277 | authors = ["{authors}"] | |
353b0b11 | 278 | license = "{spdx_lic}" |
c295e0f8 XL |
279 | edition = "2018" |
280 | [workspace] | |
281 | [dependencies] | |
282 | core_arch = {{ path = "../crates/core_arch" }} | |
283 | {binaries}"#, | |
284 | version = env!("CARGO_PKG_VERSION"), | |
285 | authors = env!("CARGO_PKG_AUTHORS"), | |
286 | binaries = intrinsics | |
287 | .iter() | |
288 | .map(|i| { | |
289 | format!( | |
290 | r#"[[bin]] | |
291 | name = "{intrinsic}" | |
292 | path = "{intrinsic}/main.rs""#, | |
293 | intrinsic = i.name | |
294 | ) | |
295 | }) | |
296 | .collect::<Vec<_>>() | |
297 | .join("\n") | |
298 | ) | |
299 | .into_bytes() | |
300 | .as_slice(), | |
301 | ) | |
302 | .unwrap(); | |
303 | ||
304 | let output = Command::new("sh") | |
305 | .current_dir("rust_programs") | |
306 | .arg("-c") | |
307 | .arg(format!( | |
f2b60f7d | 308 | "cargo {toolchain} build --target {target} --release", |
c295e0f8 | 309 | toolchain = toolchain, |
a2a8927a XL |
310 | target = if a32 { |
311 | "armv7-unknown-linux-gnueabihf" | |
312 | } else { | |
313 | "aarch64-unknown-linux-gnu" | |
314 | }, | |
c295e0f8 | 315 | )) |
a2a8927a | 316 | .env("RUSTFLAGS", "-Cdebuginfo=0") |
c295e0f8 XL |
317 | .output(); |
318 | if let Ok(output) = output { | |
319 | if output.status.success() { | |
320 | true | |
321 | } else { | |
322 | error!( | |
323 | "Failed to compile code for intrinsics\n\nstdout:\n{}\n\nstderr:\n{}", | |
324 | std::str::from_utf8(&output.stdout).unwrap_or(""), | |
325 | std::str::from_utf8(&output.stderr).unwrap_or("") | |
326 | ); | |
327 | false | |
328 | } | |
329 | } else { | |
330 | error!("Command failed: {:#?}", output); | |
331 | false | |
332 | } | |
333 | } | |
334 | ||
335 | fn main() { | |
336 | pretty_env_logger::init(); | |
337 | ||
338 | let matches = App::new("Intrinsic test tool") | |
339 | .about("Generates Rust and C programs for intrinsics and compares the output") | |
340 | .arg( | |
341 | Arg::with_name("INPUT") | |
342 | .help("The input file containing the intrinsics") | |
343 | .required(true) | |
344 | .index(1), | |
345 | ) | |
346 | .arg( | |
347 | Arg::with_name("TOOLCHAIN") | |
348 | .takes_value(true) | |
349 | .long("toolchain") | |
350 | .help("The rust toolchain to use for building the rust code"), | |
351 | ) | |
352 | .arg( | |
353 | Arg::with_name("CPPCOMPILER") | |
354 | .takes_value(true) | |
355 | .default_value("clang++") | |
356 | .long("cppcompiler") | |
357 | .help("The C++ compiler to use for compiling the c++ code"), | |
358 | ) | |
359 | .arg( | |
360 | Arg::with_name("RUNNER") | |
361 | .takes_value(true) | |
362 | .long("runner") | |
363 | .help("Run the C programs under emulation with this command"), | |
364 | ) | |
3c0e092e XL |
365 | .arg( |
366 | Arg::with_name("SKIP") | |
367 | .takes_value(true) | |
368 | .long("skip") | |
369 | .help("Filename for a list of intrinsics to skip (one per line)"), | |
370 | ) | |
a2a8927a XL |
371 | .arg( |
372 | Arg::with_name("A32") | |
373 | .takes_value(false) | |
374 | .long("a32") | |
375 | .help("Run tests for A32 instrinsics instead of A64"), | |
376 | ) | |
c295e0f8 XL |
377 | .get_matches(); |
378 | ||
379 | let filename = matches.value_of("INPUT").unwrap(); | |
380 | let toolchain = matches | |
381 | .value_of("TOOLCHAIN") | |
9ffffee4 | 382 | .map_or("".into(), |t| format!("+{t}")); |
c295e0f8 XL |
383 | |
384 | let cpp_compiler = matches.value_of("CPPCOMPILER").unwrap(); | |
385 | let c_runner = matches.value_of("RUNNER").unwrap_or(""); | |
3c0e092e XL |
386 | let skip = if let Some(filename) = matches.value_of("SKIP") { |
387 | let data = std::fs::read_to_string(&filename).expect("Failed to open file"); | |
a2a8927a XL |
388 | data.lines() |
389 | .map(str::trim) | |
390 | .filter(|s| !s.contains('#')) | |
391 | .map(String::from) | |
392 | .collect_vec() | |
3c0e092e XL |
393 | } else { |
394 | Default::default() | |
395 | }; | |
a2a8927a | 396 | let a32 = matches.is_present("A32"); |
3c0e092e | 397 | |
353b0b11 | 398 | let (csv_metadata, intrinsics) = get_acle_intrinsics(filename); |
3c0e092e XL |
399 | |
400 | let mut intrinsics = intrinsics | |
401 | .into_iter() | |
c295e0f8 XL |
402 | // Not sure how we would compare intrinsic that returns void. |
403 | .filter(|i| i.results.kind() != TypeKind::Void) | |
404 | .filter(|i| i.results.kind() != TypeKind::BFloat) | |
405 | .filter(|i| !(i.results.kind() == TypeKind::Float && i.results.inner_size() == 16)) | |
3c0e092e | 406 | .filter(|i| !i.arguments.iter().any(|a| a.ty.kind() == TypeKind::BFloat)) |
c295e0f8 | 407 | .filter(|i| { |
3c0e092e | 408 | !i.arguments |
c295e0f8 | 409 | .iter() |
3c0e092e | 410 | .any(|a| a.ty.kind() == TypeKind::Float && a.ty.inner_size() == 16) |
c295e0f8 XL |
411 | }) |
412 | // Skip pointers for now, we would probably need to look at the return | |
413 | // type to work out how many elements we need to point to. | |
3c0e092e XL |
414 | .filter(|i| !i.arguments.iter().any(|a| a.is_ptr())) |
415 | .filter(|i| !i.arguments.iter().any(|a| a.ty.inner_size() == 128)) | |
416 | .filter(|i| !skip.contains(&i.name)) | |
a2a8927a | 417 | .filter(|i| !(a32 && i.a64_only)) |
c295e0f8 XL |
418 | .collect::<Vec<_>>(); |
419 | intrinsics.dedup(); | |
420 | ||
353b0b11 FG |
421 | let notices = build_notices(&csv_metadata, "// "); |
422 | let spdx_lic = csv_metadata.spdx_license_identifier(); | |
423 | ||
424 | if !build_c(¬ices, &intrinsics, cpp_compiler, a32) { | |
c295e0f8 XL |
425 | std::process::exit(2); |
426 | } | |
427 | ||
353b0b11 | 428 | if !build_rust(¬ices, spdx_lic, &intrinsics, &toolchain, a32) { |
c295e0f8 XL |
429 | std::process::exit(3); |
430 | } | |
431 | ||
a2a8927a | 432 | if !compare_outputs(&intrinsics, &toolchain, &c_runner, a32) { |
c295e0f8 XL |
433 | std::process::exit(1) |
434 | } | |
435 | } | |
436 | ||
437 | enum FailureReason { | |
438 | RunC(String), | |
439 | RunRust(String), | |
440 | Difference(String, String, String), | |
441 | } | |
442 | ||
a2a8927a | 443 | fn compare_outputs(intrinsics: &Vec<Intrinsic>, toolchain: &str, runner: &str, a32: bool) -> bool { |
c295e0f8 XL |
444 | let intrinsics = intrinsics |
445 | .par_iter() | |
446 | .filter_map(|intrinsic| { | |
447 | let c = Command::new("sh") | |
448 | .arg("-c") | |
449 | .arg(format!( | |
450 | "{runner} ./c_programs/{intrinsic}", | |
451 | runner = runner, | |
452 | intrinsic = intrinsic.name, | |
453 | )) | |
454 | .output(); | |
455 | let rust = Command::new("sh") | |
456 | .current_dir("rust_programs") | |
457 | .arg("-c") | |
458 | .arg(format!( | |
f2b60f7d | 459 | "cargo {toolchain} run --target {target} --bin {intrinsic} --release", |
c295e0f8 XL |
460 | intrinsic = intrinsic.name, |
461 | toolchain = toolchain, | |
a2a8927a XL |
462 | target = if a32 { |
463 | "armv7-unknown-linux-gnueabihf" | |
464 | } else { | |
465 | "aarch64-unknown-linux-gnu" | |
466 | }, | |
c295e0f8 | 467 | )) |
a2a8927a | 468 | .env("RUSTFLAGS", "-Cdebuginfo=0") |
c295e0f8 XL |
469 | .output(); |
470 | ||
471 | let (c, rust) = match (c, rust) { | |
472 | (Ok(c), Ok(rust)) => (c, rust), | |
9ffffee4 | 473 | a => panic!("{a:#?}"), |
c295e0f8 XL |
474 | }; |
475 | ||
476 | if !c.status.success() { | |
477 | error!("Failed to run C program for intrinsic {}", intrinsic.name); | |
478 | return Some(FailureReason::RunC(intrinsic.name.clone())); | |
479 | } | |
480 | ||
481 | if !rust.status.success() { | |
482 | error!( | |
483 | "Failed to run rust program for intrinsic {}", | |
484 | intrinsic.name | |
485 | ); | |
486 | return Some(FailureReason::RunRust(intrinsic.name.clone())); | |
487 | } | |
488 | ||
489 | info!("Comparing intrinsic: {}", intrinsic.name); | |
490 | ||
491 | let c = std::str::from_utf8(&c.stdout) | |
492 | .unwrap() | |
493 | .to_lowercase() | |
494 | .replace("-nan", "nan"); | |
495 | let rust = std::str::from_utf8(&rust.stdout) | |
496 | .unwrap() | |
497 | .to_lowercase() | |
498 | .replace("-nan", "nan"); | |
499 | ||
500 | if c == rust { | |
501 | None | |
502 | } else { | |
503 | Some(FailureReason::Difference(intrinsic.name.clone(), c, rust)) | |
504 | } | |
505 | }) | |
506 | .collect::<Vec<_>>(); | |
507 | ||
508 | intrinsics.iter().for_each(|reason| match reason { | |
509 | FailureReason::Difference(intrinsic, c, rust) => { | |
9ffffee4 | 510 | println!("Difference for intrinsic: {intrinsic}"); |
c295e0f8 XL |
511 | let diff = diff::lines(c, rust); |
512 | diff.iter().for_each(|diff| match diff { | |
9ffffee4 FG |
513 | diff::Result::Left(c) => println!("C: {c}"), |
514 | diff::Result::Right(rust) => println!("Rust: {rust}"), | |
c295e0f8 XL |
515 | diff::Result::Both(_, _) => (), |
516 | }); | |
517 | println!("****************************************************************"); | |
518 | } | |
519 | FailureReason::RunC(intrinsic) => { | |
9ffffee4 | 520 | println!("Failed to run C program for intrinsic {intrinsic}") |
c295e0f8 XL |
521 | } |
522 | FailureReason::RunRust(intrinsic) => { | |
9ffffee4 | 523 | println!("Failed to run rust program for intrinsic {intrinsic}") |
c295e0f8 XL |
524 | } |
525 | }); | |
526 | println!("{} differences found", intrinsics.len()); | |
527 | intrinsics.is_empty() | |
528 | } |