]> git.proxmox.com Git - rustc.git/blame - library/stdarch/crates/intrinsic-test/src/main.rs
Merge 1.70 into proxmox/bookworm
[rustc.git] / library / stdarch / crates / intrinsic-test / src / main.rs
CommitLineData
3c0e092e 1#![feature(slice_partition_dedup)]
c295e0f8
XL
2#[macro_use]
3extern crate lazy_static;
4#[macro_use]
5extern crate log;
6
7use std::fs::File;
8use std::io::Write;
9use std::process::Command;
10
11use clap::{App, Arg};
12use intrinsic::Intrinsic;
3c0e092e 13use itertools::Itertools;
c295e0f8
XL
14use rayon::prelude::*;
15use types::TypeKind;
16
353b0b11 17use crate::acle_csv_parser::{get_acle_intrinsics, CsvMetadata};
3c0e092e
XL
18use crate::argument::Argument;
19
20mod acle_csv_parser;
c295e0f8
XL
21mod argument;
22mod intrinsic;
23mod types;
24mod values;
25
f2b60f7d
FG
26// The number of times each intrinsic will be called.
27const PASSES: u32 = 20;
28
c295e0f8
XL
29#[derive(Debug, PartialEq)]
30pub enum Language {
31 Rust,
32 C,
33}
34
f2b60f7d
FG
35fn 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 72fn 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
91template<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
99std::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
115int 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
134fn 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 161fn 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 174use core_arch::arch::{target_arch}::*;
c295e0f8 175
f2b60f7d
FG
176{arglists}
177
c295e0f8
XL
178fn 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 188fn 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
221fn 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
237fn 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
253fn 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 275name = "intrinsic-test-programs"
c295e0f8
XL
276version = "{version}"
277authors = ["{authors}"]
353b0b11 278license = "{spdx_lic}"
c295e0f8
XL
279edition = "2018"
280[workspace]
281[dependencies]
282core_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]]
291name = "{intrinsic}"
292path = "{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
335fn 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(&notices, &intrinsics, cpp_compiler, a32) {
c295e0f8
XL
425 std::process::exit(2);
426 }
427
353b0b11 428 if !build_rust(&notices, 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
437enum FailureReason {
438 RunC(String),
439 RunRust(String),
440 Difference(String, String, String),
441}
442
a2a8927a 443fn 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}