From 967e8d714b8bb6db98aad07197dc42228678c63a Mon Sep 17 00:00:00 2001 From: Paolo Borelli Date: Sat, 6 Feb 2021 15:45:08 +0100 Subject: [PATCH 1/2] codegen: rework ABI tests to use a single C program for each test Instead of compiling a C program for each layout and one for each constant to check, generate a single layout.c and a single constant.c which output all the values, one per line. On the rust side, it is easier to compile and run the C program just once and then parse its output one line at a time to compare with the corresponding rust record. --- src/codegen/sys/tests.rs | 277 +++++++++++++++++++-------------------- 1 file changed, 136 insertions(+), 141 deletions(-) diff --git a/src/codegen/sys/tests.rs b/src/codegen/sys/tests.rs index 53cbe2ed6..445de9d73 100644 --- a/src/codegen/sys/tests.rs +++ b/src/codegen/sys/tests.rs @@ -46,12 +46,12 @@ pub fn generate(env: &Env, crate_name: &str) { let layout_c = tests.join("layout.c"); save_to_file(&layout_c, env.config.make_backup, |w| { - generate_layout_c(env, &layout_c, w) + generate_layout_c(env, &layout_c, w, &ctypes) }); let constant_c = tests.join("constant.c"); save_to_file(&constant_c, env.config.make_backup, |w| { - generate_constant_c(env, &constant_c, w) + generate_constant_c(env, &constant_c, w, &cconsts) }); let abi_rs = tests.join("abi.rs"); @@ -202,58 +202,80 @@ fn generate_manual_h(env: &Env, path: &Path, w: &mut dyn Write) -> io::Result<() } #[allow(clippy::write_literal)] -fn generate_layout_c(env: &Env, path: &Path, w: &mut dyn Write) -> io::Result<()> { +fn generate_layout_c( + env: &Env, + path: &Path, + w: &mut dyn Write, + ctypes: &[CType], +) -> io::Result<()> { info!("Generating file {:?}", path); general::start_comments(w, &env.config)?; writeln!(w)?; writeln!(w, "#include \"manual.h\"")?; writeln!(w, "#include ")?; writeln!(w, "#include ")?; + writeln!(w)?; + writeln!(w, "{}", r"int main() {")?; - writeln!( - w, - "{}", - r##" -int main() { - printf("%zu\n%zu", sizeof(ABI_TYPE_NAME), alignof(ABI_TYPE_NAME)); - return 0; -}"## - ) + for ctype in ctypes { + writeln!( + w, + " printf(\"%s;%zu;%zu\\n\", \"{ctype}\", sizeof({ctype}), alignof({ctype}));", + ctype = ctype.name + )?; + } + + writeln!(w, " return 0;")?; + writeln!(w, "{}", r"}") } #[allow(clippy::write_literal)] -fn generate_constant_c(env: &Env, path: &Path, w: &mut dyn Write) -> io::Result<()> { +fn generate_constant_c( + env: &Env, + path: &Path, + w: &mut dyn Write, + cconsts: &[CConstant], +) -> io::Result<()> { info!("Generating file {:?}", path); general::start_comments(w, &env.config)?; writeln!(w)?; writeln!(w, "#include \"manual.h\"")?; writeln!(w, "#include ")?; - writeln!( w, "{}", r####" -int main() { - printf(_Generic((ABI_CONSTANT_NAME), - char *: "###gir test###%s###gir test###\n", - const char *: "###gir test###%s###gir test###\n", - char: "###gir test###%c###gir test###\n", - signed char: "###gir test###%hhd###gir test###\n", - unsigned char: "###gir test###%hhu###gir test###\n", - short int: "###gir test###%hd###gir test###\n", - unsigned short int: "###gir test###%hu###gir test###\n", - int: "###gir test###%d###gir test###\n", - unsigned int: "###gir test###%u###gir test###\n", - long: "###gir test###%ld###gir test###\n", - unsigned long: "###gir test###%lu###gir test###\n", - long long: "###gir test###%lld###gir test###\n", - unsigned long long: "###gir test###%llu###gir test###\n", - double: "###gir test###%f###gir test###\n", - long double: "###gir test###%ld###gir test###\n"), - ABI_CONSTANT_NAME); - return 0; -}"#### - ) +#define PRINT_CONSTANT(CONSTANT_NAME) \ + printf("%s;", #CONSTANT_NAME); \ + printf(_Generic((CONSTANT_NAME), \ + char *: "%s", \ + const char *: "%s", \ + char: "%c", \ + signed char: "%hhd", \ + unsigned char: "%hhu", \ + short int: "%hd", \ + unsigned short int: "%hu", \ + int: "%d", \ + unsigned int: "%u", \ + long: "%ld", \ + unsigned long: "%lu", \ + long long: "%lld", \ + unsigned long long: "%llu", \ + double: "%f", \ + long double: "%ld"), \ + CONSTANT_NAME); \ + printf("\n"); +"#### + )?; + + writeln!(w, "{}", r"int main() {")?; + + for cconst in cconsts { + writeln!(w, " PRINT_CONSTANT({name});", name = cconst.name,)?; + } + + writeln!(w, " return 0;")?; + writeln!(w, "{}", r"}") } #[allow(clippy::write_literal)] @@ -303,14 +325,6 @@ impl Compiler { Ok(Compiler { args }) } - pub fn define<'a, V: Into>>(&mut self, var: &str, val: V) { - let arg = match val.into() { - None => format!("-D{}", var), - Some(val) => format!("-D{}={}", var, val), - }; - self.args.push(arg); - } - pub fn compile(&self, src: &Path, out: &Path) -> Result<(), Box> { let mut cmd = self.to_command(); cmd.arg(src); @@ -318,8 +332,7 @@ impl Compiler { cmd.arg(out); let status = cmd.spawn()?.wait()?; if !status.success() { - return Err(format!("compilation command {:?} failed, {}", - &cmd, status).into()); + return Err(format!("compilation command {:?} failed, {}", &cmd, status).into()); } Ok(()) } @@ -370,8 +383,6 @@ struct Results { passed: usize, /// Total number of failed tests (including those that failed to compile). failed: usize, - /// Number of tests that failed to compile. - failed_to_compile: usize, } impl Results { @@ -381,16 +392,8 @@ impl Results { fn record_failed(&mut self) { self.failed += 1; } - fn record_failed_to_compile(&mut self) { - self.failed += 1; - self.failed_to_compile += 1; - } fn summary(&self) -> String { - format!( - "{} passed; {} failed (compilation errors: {})", - self.passed, - self.failed, - self.failed_to_compile) + format!("{} passed; {} failed", self.passed, self.failed) } fn expect_total_success(&self) { if self.failed == 0 { @@ -403,118 +406,110 @@ impl Results { #[test] fn cross_validate_constants_with_c() { - let tmpdir = Builder::new().prefix("abi").tempdir().expect("temporary directory"); - let cc = Compiler::new().expect("configured compiler"); + let mut c_constants: Vec<(String, String)> = Vec::new(); + + for l in get_c_output("constant").unwrap().lines() { + let mut words = l.trim().split(";"); + let name = words.next().expect("Failed to parse name").to_owned(); + let value = words + .next() + .and_then(|s| s.parse().ok()) + .expect("Failed to parse value"); + c_constants.push((name, value)); + } - assert_eq!("1", - get_c_value(tmpdir.path(), &cc, "1").expect("C constant"), - "failed to obtain correct constant value for 1"); - - let mut results : Results = Default::default(); - for (i, &(name, rust_value)) in RUST_CONSTANTS.iter().enumerate() { - match get_c_value(tmpdir.path(), &cc, name) { - Err(e) => { - results.record_failed_to_compile(); - eprintln!("{}", e); - }, - Ok(ref c_value) => { - if rust_value == c_value { - results.record_passed(); - } else { - results.record_failed(); - eprintln!("Constant value mismatch for {}\nRust: {:?}\nC: {:?}", - name, rust_value, c_value); - } - } - }; - if (i + 1) % 25 == 0 { - println!("constants ... {}", results.summary()); + let mut results = Results::default(); + + for ((rust_name, rust_value), (c_name, c_value)) in + RUST_CONSTANTS.iter().zip(c_constants.iter()) + { + if rust_name != c_name { + results.record_failed(); + eprintln!("Name mismatch:\nRust: {:?}\nC: {:?}", rust_name, c_name,); + continue; } + + if rust_value != c_value { + results.record_failed(); + eprintln!( + "Constant value mismatch for {}\nRust: {:?}\nC: {:?}", + rust_name, rust_value, &c_value + ); + continue; + } + + results.record_passed(); } + results.expect_total_success(); } #[test] fn cross_validate_layout_with_c() { - let tmpdir = Builder::new().prefix("abi").tempdir().expect("temporary directory"); - let cc = Compiler::new().expect("configured compiler"); + let mut c_layouts = Vec::new(); + + for l in get_c_output("layout").unwrap().lines() { + let mut words = l.trim().split(";"); + let name = words.next().expect("Failed to parse name").to_owned(); + let size = words + .next() + .and_then(|s| s.parse().ok()) + .expect("Failed to parse size"); + let alignment = words + .next() + .and_then(|s| s.parse().ok()) + .expect("Failed to parse alignment"); + c_layouts.push((name, Layout { size, alignment })); + } - assert_eq!(Layout {size: 1, alignment: 1}, - get_c_layout(tmpdir.path(), &cc, "char").expect("C layout"), - "failed to obtain correct layout for char type"); - - let mut results : Results = Default::default(); - for (i, &(name, rust_layout)) in RUST_LAYOUTS.iter().enumerate() { - match get_c_layout(tmpdir.path(), &cc, name) { - Err(e) => { - results.record_failed_to_compile(); - eprintln!("{}", e); - }, - Ok(c_layout) => { - if rust_layout == c_layout { - results.record_passed(); - } else { - results.record_failed(); - eprintln!("Layout mismatch for {}\nRust: {:?}\nC: {:?}", - name, rust_layout, &c_layout); - } - } - }; - if (i + 1) % 25 == 0 { - println!("layout ... {}", results.summary()); + let mut results = Results::default(); + + for ((rust_name, rust_layout), (c_name, c_layout)) in + RUST_LAYOUTS.iter().zip(c_layouts.iter()) + { + if rust_name != c_name { + results.record_failed(); + eprintln!("Name mismatch:\nRust: {:?}\nC: {:?}", rust_name, c_name,); + continue; } - } - results.expect_total_success(); -} -fn get_c_layout(dir: &Path, cc: &Compiler, name: &str) -> Result> { - let exe = dir.join("layout"); - let mut cc = cc.clone(); - cc.define("ABI_TYPE_NAME", name); - cc.compile(Path::new("tests/layout.c"), &exe)?; + if rust_layout != c_layout { + results.record_failed(); + eprintln!( + "Layout mismatch for {}\nRust: {:?}\nC: {:?}", + rust_name, rust_layout, &c_layout + ); + continue; + } - let mut abi_cmd = Command::new(exe); - let output = abi_cmd.output()?; - if !output.status.success() { - return Err(format!("command {:?} failed, {:?}", - &abi_cmd, &output).into()); + results.record_passed(); } - let stdout = str::from_utf8(&output.stdout)?; - let mut words = stdout.trim().split_whitespace(); - let size = words.next().unwrap().parse().unwrap(); - let alignment = words.next().unwrap().parse().unwrap(); - Ok(Layout {size, alignment}) + results.expect_total_success(); } -fn get_c_value(dir: &Path, cc: &Compiler, name: &str) -> Result> { - let exe = dir.join("constant"); - let mut cc = cc.clone(); - cc.define("ABI_CONSTANT_NAME", name); - cc.compile(Path::new("tests/constant.c"), &exe)?; +fn get_c_output(name: &str) -> Result> { + let tmpdir = Builder::new().prefix("abi").tempdir()?; + let exe = tmpdir.path().join(name); + let c_file = Path::new("tests").join(name).with_extension("c"); + + let cc = Compiler::new().expect("configured compiler"); + cc.compile(&c_file, &exe)?; let mut abi_cmd = Command::new(exe); let output = abi_cmd.output()?; if !output.status.success() { - return Err(format!("command {:?} failed, {:?}", - &abi_cmd, &output).into()); - } - - let output = str::from_utf8(&output.stdout)?.trim(); - if !output.starts_with("###gir test###") || - !output.ends_with("###gir test###") { - return Err(format!("command {:?} return invalid output, {:?}", - &abi_cmd, &output).into()); + return Err(format!("command {:?} failed, {:?}", &abi_cmd, &output).into()); } - Ok(String::from(&output[14..(output.len() - 14)])) + Ok(String::from_utf8(output.stdout)?) } const RUST_LAYOUTS: &[(&str, Layout)] = &["#### )?; for ctype in ctypes { general::cfg_condition(w, &ctype.cfg_condition, false, 1)?; - writeln!(w, "\t(\"{ctype}\", Layout {{size: size_of::<{ctype}>(), alignment: align_of::<{ctype}>()}}),", + writeln!(w, " (\"{ctype}\", Layout {{size: size_of::<{ctype}>(), alignment: align_of::<{ctype}>()}}),", ctype=ctype.name)?; } writeln!( @@ -527,7 +522,7 @@ const RUST_CONSTANTS: &[(&str, &str)] = &["## for cconst in cconsts { writeln!( w, - "\t(\"{name}\", \"{value}\"),", + " (\"{name}\", \"{value}\"),", name = cconst.name, value = &general::escape_string(&cconst.value) )?; From 11538b2407dd750aeed5134ac3a827ff1bef4c03 Mon Sep 17 00:00:00 2001 From: Paolo Borelli Date: Sat, 6 Feb 2021 21:31:57 +0100 Subject: [PATCH 2/2] codegen: pass -std=c11 to the abi tests, since we use _Generic --- src/codegen/sys/tests.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/codegen/sys/tests.rs b/src/codegen/sys/tests.rs index 445de9d73..156d3a6c5 100644 --- a/src/codegen/sys/tests.rs +++ b/src/codegen/sys/tests.rs @@ -317,6 +317,8 @@ impl Compiler { pub fn new() -> Result> { let mut args = get_var("CC", "cc")?; args.push("-Wno-deprecated-declarations".to_owned()); + // For _Generic + args.push("-std=c11".to_owned()); // For %z support in printf when using MinGW. args.push("-D__USE_MINGW_ANSI_STDIO".to_owned()); args.extend(get_var("CFLAGS", "")?);