]> git.proxmox.com Git - rustc.git/blob - library/stdarch/crates/intrinsic-test/src/main.rs
New upstream version 1.57.0+dfsg1
[rustc.git] / library / stdarch / crates / intrinsic-test / src / main.rs
1 #[macro_use]
2 extern crate lazy_static;
3 #[macro_use]
4 extern crate log;
5
6 use std::fs::File;
7 use std::io::Write;
8 use std::process::Command;
9
10 use clap::{App, Arg};
11 use intrinsic::Intrinsic;
12 use rayon::prelude::*;
13 use types::TypeKind;
14
15 mod argument;
16 mod intrinsic;
17 mod types;
18 mod values;
19
20 #[derive(Debug, PartialEq)]
21 pub enum Language {
22 Rust,
23 C,
24 }
25
26 fn generate_c_program(header_files: &[&str], intrinsic: &Intrinsic) -> String {
27 format!(
28 r#"{header_files}
29 #include <iostream>
30 #include <cstring>
31 #include <iomanip>
32 #include <sstream>
33 template<typename T1, typename T2> T1 cast(T2 x) {{
34 static_assert(sizeof(T1) == sizeof(T2), "sizeof T1 and T2 must be the same");
35 T1 ret = 0;
36 memcpy(&ret, &x, sizeof(T1));
37 return ret;
38 }}
39 std::ostream& operator<<(std::ostream& os, poly128_t value) {{
40 std::stringstream temp;
41 do {{
42 int n = value % 10;
43 value /= 10;
44 temp << n;
45 }} while (value != 0);
46 std::string tempstr(temp.str());
47 std::string res(tempstr.rbegin(), tempstr.rend());
48 os << res;
49 return os;
50 }}
51 int main(int argc, char **argv) {{
52 {passes}
53 return 0;
54 }}"#,
55 header_files = header_files
56 .iter()
57 .map(|header| format!("#include <{}>", header))
58 .collect::<Vec<_>>()
59 .join("\n"),
60 passes = (1..20)
61 .map(|idx| intrinsic.generate_pass_c(idx))
62 .collect::<Vec<_>>()
63 .join("\n"),
64 )
65 }
66
67 fn generate_rust_program(intrinsic: &Intrinsic) -> String {
68 format!(
69 r#"#![feature(simd_ffi)]
70 #![feature(link_llvm_intrinsics)]
71 #![feature(stdsimd)]
72 #![allow(overflowing_literals)]
73 use core_arch::arch::aarch64::*;
74
75 fn main() {{
76 {passes}
77 }}
78 "#,
79 passes = (1..20)
80 .map(|idx| intrinsic.generate_pass_rust(idx))
81 .collect::<Vec<_>>()
82 .join("\n"),
83 )
84 }
85
86 fn compile_c(c_filename: &str, intrinsic: &Intrinsic, compiler: &str) -> bool {
87 let flags = std::env::var("CPPFLAGS").unwrap_or("".into());
88
89 let output = Command::new("sh")
90 .arg("-c")
91 .arg(format!(
92 "{cpp} {cppflags} {arch_flags} -Wno-narrowing -O2 -target {target} -o c_programs/{intrinsic} {filename}",
93 target = "aarch64-unknown-linux-gnu",
94 arch_flags = "-march=armv8-a+crypto+crc",
95 filename = c_filename,
96 intrinsic = intrinsic.name,
97 cpp = compiler,
98 cppflags = flags,
99 ))
100 .output();
101 if let Ok(output) = output {
102 if output.status.success() {
103 true
104 } else {
105 let stderr = std::str::from_utf8(&output.stderr).unwrap_or("");
106 if stderr.contains("error: use of undeclared identifier") {
107 warn!("Skipping intrinsic due to no support: {}", intrinsic.name);
108 true
109 } else {
110 error!(
111 "Failed to compile code for intrinsic: {}\n\nstdout:\n{}\n\nstderr:\n{}",
112 intrinsic.name,
113 std::str::from_utf8(&output.stdout).unwrap_or(""),
114 std::str::from_utf8(&output.stderr).unwrap_or("")
115 );
116 false
117 }
118 }
119 } else {
120 error!("Command failed: {:#?}", output);
121 false
122 }
123 }
124
125 fn build_c(intrinsics: &Vec<Intrinsic>, compiler: &str) -> bool {
126 let _ = std::fs::create_dir("c_programs");
127 intrinsics
128 .par_iter()
129 .map(|i| {
130 let c_filename = format!(r#"c_programs/{}.cpp"#, i.name);
131 let mut file = File::create(&c_filename).unwrap();
132
133 let c_code = generate_c_program(&["arm_neon.h", "arm_acle.h"], &i);
134 file.write_all(c_code.into_bytes().as_slice()).unwrap();
135 compile_c(&c_filename, &i, compiler)
136 })
137 .find_any(|x| !x)
138 .is_none()
139 }
140
141 fn build_rust(intrinsics: &Vec<Intrinsic>, toolchain: &str) -> bool {
142 intrinsics.iter().for_each(|i| {
143 let rust_dir = format!(r#"rust_programs/{}"#, i.name);
144 let _ = std::fs::create_dir_all(&rust_dir);
145 let rust_filename = format!(r#"{}/main.rs"#, rust_dir);
146 let mut file = File::create(&rust_filename).unwrap();
147
148 let c_code = generate_rust_program(&i);
149 file.write_all(c_code.into_bytes().as_slice()).unwrap();
150 });
151
152 let mut cargo = File::create("rust_programs/Cargo.toml").unwrap();
153 cargo
154 .write_all(
155 format!(
156 r#"[package]
157 name = "intrinsic-test"
158 version = "{version}"
159 authors = ["{authors}"]
160 edition = "2018"
161 [workspace]
162 [dependencies]
163 core_arch = {{ path = "../crates/core_arch" }}
164 {binaries}"#,
165 version = env!("CARGO_PKG_VERSION"),
166 authors = env!("CARGO_PKG_AUTHORS"),
167 binaries = intrinsics
168 .iter()
169 .map(|i| {
170 format!(
171 r#"[[bin]]
172 name = "{intrinsic}"
173 path = "{intrinsic}/main.rs""#,
174 intrinsic = i.name
175 )
176 })
177 .collect::<Vec<_>>()
178 .join("\n")
179 )
180 .into_bytes()
181 .as_slice(),
182 )
183 .unwrap();
184
185 let output = Command::new("sh")
186 .current_dir("rust_programs")
187 .arg("-c")
188 .arg(format!(
189 "cargo {toolchain} build --release --target {target}",
190 toolchain = toolchain,
191 target = "aarch64-unknown-linux-gnu",
192 ))
193 .output();
194 if let Ok(output) = output {
195 if output.status.success() {
196 true
197 } else {
198 error!(
199 "Failed to compile code for intrinsics\n\nstdout:\n{}\n\nstderr:\n{}",
200 std::str::from_utf8(&output.stdout).unwrap_or(""),
201 std::str::from_utf8(&output.stderr).unwrap_or("")
202 );
203 false
204 }
205 } else {
206 error!("Command failed: {:#?}", output);
207 false
208 }
209 }
210
211 fn main() {
212 pretty_env_logger::init();
213
214 let matches = App::new("Intrinsic test tool")
215 .about("Generates Rust and C programs for intrinsics and compares the output")
216 .arg(
217 Arg::with_name("INPUT")
218 .help("The input file containing the intrinsics")
219 .required(true)
220 .index(1),
221 )
222 .arg(
223 Arg::with_name("TOOLCHAIN")
224 .takes_value(true)
225 .long("toolchain")
226 .help("The rust toolchain to use for building the rust code"),
227 )
228 .arg(
229 Arg::with_name("CPPCOMPILER")
230 .takes_value(true)
231 .default_value("clang++")
232 .long("cppcompiler")
233 .help("The C++ compiler to use for compiling the c++ code"),
234 )
235 .arg(
236 Arg::with_name("RUNNER")
237 .takes_value(true)
238 .long("runner")
239 .help("Run the C programs under emulation with this command"),
240 )
241 .get_matches();
242
243 let filename = matches.value_of("INPUT").unwrap();
244 let toolchain = matches
245 .value_of("TOOLCHAIN")
246 .map_or("".into(), |t| format!("+{}", t));
247
248 let cpp_compiler = matches.value_of("CPPCOMPILER").unwrap();
249 let c_runner = matches.value_of("RUNNER").unwrap_or("");
250 let mut csv_reader = csv::Reader::from_reader(std::fs::File::open(filename).unwrap());
251
252 let mut intrinsics = csv_reader
253 .deserialize()
254 .filter_map(|x| -> Option<Intrinsic> {
255 debug!("Processing {:#?}", x);
256 match x {
257 Ok(a) => Some(a),
258 e => {
259 error!("{:#?}", e);
260 None
261 }
262 }
263 })
264 // Only perform the test for intrinsics that are enabled...
265 .filter(|i| i.enabled)
266 // Not sure how we would compare intrinsic that returns void.
267 .filter(|i| i.results.kind() != TypeKind::Void)
268 .filter(|i| i.results.kind() != TypeKind::BFloat)
269 .filter(|i| !(i.results.kind() == TypeKind::Float && i.results.inner_size() == 16))
270 .filter(|i| {
271 i.arguments
272 .iter()
273 .find(|a| a.ty.kind() == TypeKind::BFloat)
274 .is_none()
275 })
276 .filter(|i| {
277 i.arguments
278 .iter()
279 .find(|a| a.ty.kind() == TypeKind::Float && a.ty.inner_size() == 16)
280 .is_none()
281 })
282 // Skip pointers for now, we would probably need to look at the return
283 // type to work out how many elements we need to point to.
284 .filter(|i| i.arguments.iter().find(|a| a.is_ptr()).is_none())
285 // intrinsics with a lane parameter have constraints, deal with them later.
286 .filter(|i| {
287 i.arguments
288 .iter()
289 .find(|a| a.name.starts_with("lane"))
290 .is_none()
291 })
292 .filter(|i| i.arguments.iter().find(|a| a.name == "n").is_none())
293 // clang-12 fails to compile this intrinsic due to an error.
294 // fatal error: error in backend: Cannot select: 0x2b99c30: i64 = AArch64ISD::VSHL Constant:i64<1>, Constant:i32<1>
295 // 0x2b9a520: i64 = Constant<1>
296 // 0x2b9a248: i32 = Constant<1>
297 .filter(|i| !["vshld_s64", "vshld_u64"].contains(&i.name.as_str()))
298 .collect::<Vec<_>>();
299 intrinsics.dedup();
300
301 if !build_c(&intrinsics, cpp_compiler) {
302 std::process::exit(2);
303 }
304
305 if !build_rust(&intrinsics, &toolchain) {
306 std::process::exit(3);
307 }
308
309 if !compare_outputs(&intrinsics, &toolchain, &c_runner) {
310 std::process::exit(1)
311 }
312 }
313
314 enum FailureReason {
315 RunC(String),
316 RunRust(String),
317 Difference(String, String, String),
318 }
319
320 fn compare_outputs(intrinsics: &Vec<Intrinsic>, toolchain: &str, runner: &str) -> bool {
321 let intrinsics = intrinsics
322 .par_iter()
323 .filter_map(|intrinsic| {
324 let c = Command::new("sh")
325 .arg("-c")
326 .arg(format!(
327 "{runner} ./c_programs/{intrinsic}",
328 runner = runner,
329 intrinsic = intrinsic.name,
330 ))
331 .output();
332 let rust = Command::new("sh")
333 .current_dir("rust_programs")
334 .arg("-c")
335 .arg(format!(
336 "cargo {toolchain} run --release --target {target} --bin {intrinsic}",
337 intrinsic = intrinsic.name,
338 toolchain = toolchain,
339 target = "aarch64-unknown-linux-gnu",
340 ))
341 .output();
342
343 let (c, rust) = match (c, rust) {
344 (Ok(c), Ok(rust)) => (c, rust),
345 a => panic!("{:#?}", a),
346 };
347
348 if !c.status.success() {
349 error!("Failed to run C program for intrinsic {}", intrinsic.name);
350 return Some(FailureReason::RunC(intrinsic.name.clone()));
351 }
352
353 if !rust.status.success() {
354 error!(
355 "Failed to run rust program for intrinsic {}",
356 intrinsic.name
357 );
358 return Some(FailureReason::RunRust(intrinsic.name.clone()));
359 }
360
361 info!("Comparing intrinsic: {}", intrinsic.name);
362
363 let c = std::str::from_utf8(&c.stdout)
364 .unwrap()
365 .to_lowercase()
366 .replace("-nan", "nan");
367 let rust = std::str::from_utf8(&rust.stdout)
368 .unwrap()
369 .to_lowercase()
370 .replace("-nan", "nan");
371
372 if c == rust {
373 None
374 } else {
375 Some(FailureReason::Difference(intrinsic.name.clone(), c, rust))
376 }
377 })
378 .collect::<Vec<_>>();
379
380 intrinsics.iter().for_each(|reason| match reason {
381 FailureReason::Difference(intrinsic, c, rust) => {
382 println!("Difference for intrinsic: {}", intrinsic);
383 let diff = diff::lines(c, rust);
384 diff.iter().for_each(|diff| match diff {
385 diff::Result::Left(c) => println!("C: {}", c),
386 diff::Result::Right(rust) => println!("Rust: {}", rust),
387 diff::Result::Both(_, _) => (),
388 });
389 println!("****************************************************************");
390 }
391 FailureReason::RunC(intrinsic) => {
392 println!("Failed to run C program for intrinsic {}", intrinsic)
393 }
394 FailureReason::RunRust(intrinsic) => {
395 println!("Failed to run rust program for intrinsic {}", intrinsic)
396 }
397 });
398 println!("{} differences found", intrinsics.len());
399 intrinsics.is_empty()
400 }