diff --git a/src/abis/mod.rs b/src/abis/mod.rs index 5314bb9..5ba919b 100644 --- a/src/abis/mod.rs +++ b/src/abis/mod.rs @@ -39,8 +39,6 @@ pub static ALL_CONVENTIONS: &[CallingConvention] = &[ // CallingConvention::Aapcs, ]; -pub static OUTPUT_NAME: &str = "output"; - /// A test case, fully abstract. /// /// An abi-cafe Test is essentially a series of function signatures @@ -229,6 +227,7 @@ impl std::ops::Deref for TestImpl { #[derive(Debug, Clone, Serialize, PartialEq, Eq)] pub enum WriteImpl { HarnessCallback, + Assert, Print, Noop, } @@ -258,8 +257,7 @@ pub trait AbiImpl { } impl Test { - pub fn has_convention(&self, convention: CallingConvention) -> bool { - // TODO + pub fn has_convention(&self, _convention: CallingConvention) -> bool { true } pub async fn for_abi( diff --git a/src/abis/rust.rs b/src/abis/rust.rs index 908bc47..50212fd 100644 --- a/src/abis/rust.rs +++ b/src/abis/rust.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use camino::Utf8Path; use kdl_script::types::{AliasTy, ArrayTy, Func, FuncIdx, PrimitiveTy, RefTy, Ty, TyIdx}; use kdl_script::PunEnv; -use vals::ArgValuesIter; +use vals::{ArgValuesIter, Value}; use self::error::GenerateError; @@ -76,6 +76,7 @@ impl AbiImpl for RustcAbiImpl { let supports_writer = match val_writer { WriteImpl::HarnessCallback => true, WriteImpl::Print => true, + WriteImpl::Assert => true, WriteImpl::Noop => true, }; let supports_query = match functions { @@ -451,6 +452,10 @@ impl RustcAbiImpl { ((*tagged_ty.name).clone(), borrowed_tyname) } Ty::Alias(AliasTy { name, real, attrs }) => { + assert!( + attrs.is_empty(), + "don't yet know how to apply attrs to structs" + ); let borrowed_tyname = state .borrowed_tynames .get(real) @@ -557,7 +562,7 @@ impl RustcAbiImpl { // Emit an actual enum decl writeln!(f, "#[repr(C)]")?; - writeln!(f, "#[derive(Copy, Clone)]")?; + writeln!(f, "#[derive(Debug, Copy, Clone, PartialEq)]")?; writeln!(f, "enum {} {{", enum_ty.name)?; f.add_indent(1); for variant in &enum_ty.variants { @@ -669,6 +674,64 @@ impl RustcAbiImpl { Ok(()) } + pub fn generate_leaf_value( + &self, + f: &mut Fivemat, + state: &TestImpl, + ty: TyIdx, + val: &Value, + alias: Option<&str>, + ) -> Result<(), GenerateError> { + match state.types.realize_ty(ty) { + // Primitives are the only "real" values with actual bytes that advance val_idx + Ty::Primitive(prim) => match prim { + PrimitiveTy::I8 => write!(f, "{}i8", val.generate_u8() as i8)?, + PrimitiveTy::I16 => write!(f, "{}i16", val.generate_u16() as i16)?, + PrimitiveTy::I32 => write!(f, "{}i32", val.generate_u32() as i32)?, + PrimitiveTy::I64 => write!(f, "{}i64", val.generate_u64() as i64)?, + PrimitiveTy::I128 => write!(f, "{}i128", val.generate_u128() as i128)?, + PrimitiveTy::U8 => write!(f, "{}u8", val.generate_u8())?, + PrimitiveTy::U16 => write!(f, "{}u16", val.generate_u16())?, + PrimitiveTy::U32 => write!(f, "{}u32", val.generate_u32())?, + PrimitiveTy::U64 => write!(f, "{}u64", val.generate_u64())?, + PrimitiveTy::U128 => write!(f, "{}u128", val.generate_u128())?, + + PrimitiveTy::F32 => write!(f, "f32::from_bits({})", val.generate_u32())?, + PrimitiveTy::F64 => write!(f, "f64::from_bits({})", val.generate_u64())?, + PrimitiveTy::Bool => write!(f, "true")?, + PrimitiveTy::Ptr => { + if true { + write!(f, "{:#X}u64 as *mut ()", val.generate_u64())? + } else { + write!(f, "{:#X}u32 as *mut ()", val.generate_u32())? + } + } + PrimitiveTy::I256 => Err(GenerateError::RustUnsupported( + "rust doesn't have i256".to_owned(), + ))?, + PrimitiveTy::U256 => Err(GenerateError::RustUnsupported( + "rust doesn't have u256".to_owned(), + ))?, + PrimitiveTy::F16 => Err(GenerateError::RustUnsupported( + "rust doesn't have f16".to_owned(), + ))?, + PrimitiveTy::F128 => Err(GenerateError::RustUnsupported( + "rust doesn't have f128".to_owned(), + ))?, + }, + Ty::Enum(enum_ty) => { + let name = alias.unwrap_or(&enum_ty.name); + if let Some(variant) = val.select_val(&enum_ty.variants) { + let variant_name = &variant.name; + write!(f, "{name}::{variant_name}")?; + } + } + _ => unreachable!("only primitives and enums should be passed to generate_leaf_value"), + } + Ok(()) + } + + #[allow(clippy::too_many_arguments)] pub fn generate_value( &self, f: &mut Fivemat, @@ -680,44 +743,10 @@ impl RustcAbiImpl { extra_decls: &mut Vec, ) -> Result<(), GenerateError> { match state.types.realize_ty(ty) { - // Primitives are the only "real" values with actual bytes that advance val_idx - Ty::Primitive(prim) => { + // Primitives and Enums are the only "real" values with actual bytes + Ty::Primitive(_) | Ty::Enum(_) => { let val = vals.next_val(); - match prim { - PrimitiveTy::I8 => write!(f, "{:#X}u8 as i8", val.generate_u8())?, - PrimitiveTy::I16 => write!(f, "{:#X}u16 as i16", val.generate_u16())?, - PrimitiveTy::I32 => write!(f, "{:#X}u32 as i32", val.generate_u32())?, - PrimitiveTy::I64 => write!(f, "{:#X}u64 as i64", val.generate_u64())?, - PrimitiveTy::I128 => write!(f, "{:#X}u128 as i128", val.generate_u128())?, - PrimitiveTy::U8 => write!(f, "{:#X}", val.generate_u8())?, - PrimitiveTy::U16 => write!(f, "{:#X}", val.generate_u16())?, - PrimitiveTy::U32 => write!(f, "{:#X}", val.generate_u32())?, - PrimitiveTy::U64 => write!(f, "{:#X}", val.generate_u64())?, - PrimitiveTy::U128 => write!(f, "{:#X}", val.generate_u128())?, - - PrimitiveTy::F32 => write!(f, "f32::from_bits({:#X})", val.generate_u32())?, - PrimitiveTy::F64 => write!(f, "f64::from_bits({:#X})", val.generate_u64())?, - PrimitiveTy::Bool => write!(f, "true")?, - PrimitiveTy::Ptr => { - if true { - write!(f, "{:#X} as *mut ()", val.generate_u64())? - } else { - write!(f, "{:#X} as *mut ()", val.generate_u32())? - } - } - PrimitiveTy::I256 => Err(GenerateError::RustUnsupported( - "rust doesn't have i256".to_owned(), - ))?, - PrimitiveTy::U256 => Err(GenerateError::RustUnsupported( - "rust doesn't have u256".to_owned(), - ))?, - PrimitiveTy::F16 => Err(GenerateError::RustUnsupported( - "rust doesn't have f16".to_owned(), - ))?, - PrimitiveTy::F128 => Err(GenerateError::RustUnsupported( - "rust doesn't have f128".to_owned(), - ))?, - }; + self.generate_leaf_value(f, state, ty, &val, alias)?; } Ty::Empty => { write!(f, "()")?; @@ -730,8 +759,7 @@ impl RustcAbiImpl { // Now do the rest of the recursion on constructing the temporary let mut ref_temp = String::new(); let mut ref_temp_f = Fivemat::new(&mut ref_temp, INDENT); - let ty_name = &state.tynames[pointee_ty]; - write!(&mut ref_temp_f, "let mut {ref_temp_name}: {ty_name} = ")?; + write!(&mut ref_temp_f, "let mut {ref_temp_name} = ")?; let ref_temp_name = format!("{ref_temp_name}_"); self.generate_value( &mut ref_temp_f, @@ -807,14 +835,7 @@ impl RustcAbiImpl { } write!(f, " }}")?; } - Ty::Enum(enum_ty) => { - let name = alias.unwrap_or(&enum_ty.name); - let tag_val = vals.next_val(); - if let Some(variant) = tag_val.select_val(&enum_ty.variants) { - let variant_name = &variant.name; - write!(f, "{name}::{variant_name}")?; - } - } + Ty::Tagged(tagged_ty) => { let name = alias.unwrap_or(&tagged_ty.name); let tag_val = vals.next_val(); @@ -969,13 +990,12 @@ impl RustcAbiImpl { mut vals: ArgValuesIter, ) -> Result<(), GenerateError> { // Generate the input - let ty_name = &state.tynames[&var_ty]; let needs_mut = false; let let_mut = if needs_mut { "let mut" } else { "let" }; let mut real_var_decl = String::new(); let mut real_var_decl_f = Fivemat::new(&mut real_var_decl, INDENT); let mut extra_decls = Vec::new(); - write!(&mut real_var_decl_f, "{let_mut} {var_name}: {ty_name} = ")?; + write!(&mut real_var_decl_f, "{let_mut} {var_name} = ")?; let ref_temp_name = format!("{var_name}_"); self.generate_value( &mut real_var_decl_f, @@ -1041,7 +1061,7 @@ impl RustcAbiImpl { // Hey an actual leaf, report it (and burn a value) let val = vals.next_val(); if val.should_write_val(&state.options) { - self.write_leaf_field(f, state, to, from)?; + self.write_leaf_field(f, state, to, from, &val)?; } } Ty::Empty => { @@ -1093,28 +1113,52 @@ impl RustcAbiImpl { format!("{tagged_name}::{variant_name}") } }; - // Generate an if-let - writeln!(f, "if let {pat} = &{from} {{")?; - f.add_indent(1); - if tag_generator.should_write_val(&state.options) { - self.write_tag_field(f, state, to, tag_idx)?; - } - if let Some(fields) = &variant.fields { - for field in fields { - // Do the ugly deref thing to deal with pattern autoref - let base = format!("(*{})", field.ident); - self.write_fields(f, state, to, &base, field.ty, vals)?; + + // We're going to make an if-let for the case we expect, but there might not + // be anything we care about in here (especially with should_write_val) so we + // buffer up if and else branches and then only emit the if-let if one of them + // is non-empty + let if_branch = { + let mut temp_out = String::new(); + let f = &mut Fivemat::new(&mut temp_out, INDENT); + f.add_indent(1); + if tag_generator.should_write_val(&state.options) { + self.write_tag_field(f, state, to, tag_idx)?; } - } - f.sub_indent(1); + if let Some(fields) = &variant.fields { + for field in fields { + // Do the ugly deref thing to deal with pattern autoref + let base = format!("(*{})", field.ident); + self.write_fields(f, state, to, &base, field.ty, vals)?; + } + } + f.sub_indent(1); + temp_out + }; // Add an else case to complain that the variant is wrong - writeln!(f, "}} else {{")?; - f.add_indent(1); - if tag_generator.should_write_val(&state.options) { - self.error_tag_field(f, state, to)?; + let else_branch = { + let mut temp_out = String::new(); + let f = &mut Fivemat::new(&mut temp_out, INDENT); + f.add_indent(1); + if tag_generator.should_write_val(&state.options) { + self.error_tag_field(f, state, to)?; + } + f.sub_indent(1); + temp_out + }; + + let if_has_content = !if_branch.trim().is_empty(); + let else_has_content = !else_branch.trim().is_empty(); + if if_has_content || else_has_content { + writeln!(f, "if let {pat} = &{from} {{")?; + write!(f, "{}", if_branch)?; + write!(f, "}}")?; + } + if else_has_content { + writeln!(f, " else {{")?; + write!(f, "{}", else_branch)?; + writeln!(f, "}}")?; } - f.sub_indent(1); - writeln!(f, "}}")?; } } Ty::Ref(ref_ty) => { @@ -1146,10 +1190,21 @@ impl RustcAbiImpl { state: &TestImpl, to: &str, path: &str, + val: &Value, ) -> Result<(), GenerateError> { match state.options.val_writer { WriteImpl::HarnessCallback => { - writeln!(f, "write_field({to}, &{path});")?; + // Convenience for triggering test failures + if path.contains("abicafepoison") && to.contains(VAR_CALLEE_INPUTS) { + writeln!(f, "write_field({to}, &0x12345678u32);")?; + } else { + writeln!(f, "write_field({to}, &{path});")?; + } + } + WriteImpl::Assert => { + write!(f, "assert_eq!({path}, ")?; + self.generate_leaf_value(f, state, val.ty, val, None)?; + writeln!(f, ");")?; } WriteImpl::Print => { writeln!(f, "println!(\"{{:?}}\", {path});")?; @@ -1172,6 +1227,9 @@ impl RustcAbiImpl { WriteImpl::HarnessCallback => { writeln!(f, "write_field({to}, &{}u32);", variant_idx)?; } + WriteImpl::Assert => { + // Noop, do nothing + } WriteImpl::Print => { // Noop, do nothing } @@ -1192,6 +1250,9 @@ impl RustcAbiImpl { WriteImpl::HarnessCallback => { writeln!(f, "write_field({to}, &{}u32);", u32::MAX)?; } + WriteImpl::Assert => { + unreachable!("enum had unexpected variant!?"); + } WriteImpl::Print => { unreachable!("enum had unexpected variant!?"); } @@ -1213,7 +1274,7 @@ impl RustcAbiImpl { WriteImpl::HarnessCallback => { writeln!(f, "finished_func({inputs}, {outputs});")?; } - WriteImpl::Print | WriteImpl::Noop => { + WriteImpl::Print | WriteImpl::Noop | WriteImpl::Assert => { // Noop } } diff --git a/src/abis/vals.rs b/src/abis/vals.rs index 3e654cb..f761e56 100644 --- a/src/abis/vals.rs +++ b/src/abis/vals.rs @@ -194,7 +194,7 @@ impl<'a> std::ops::Deref for ValueRef<'a> { &self.tree.funcs[self.func_idx].args[self.arg_idx].vals[self.val_idx] } } -impl<'a> std::ops::Deref for Value { +impl std::ops::Deref for Value { type Target = ValueGenerator; fn deref(&self) -> &Self::Target { &self.val diff --git a/src/harness/check.rs b/src/harness/check.rs index 870e489..461d1f7 100644 --- a/src/harness/check.rs +++ b/src/harness/check.rs @@ -25,25 +25,8 @@ impl TestHarness { let mut results: Vec> = Vec::new(); - // As a basic sanity-check, make sure everything agrees on how - // many tests actually executed. If this fails, then something - // is very fundamentally broken and needs to be fixed. + // `Run` already checks that this length is congruent with all the inputs/outputs Vecs let expected_funcs = key.options.functions.active_funcs(&test.types); - let expected_test_count = expected_funcs.len(); - if caller_inputs.funcs.len() != expected_test_count - || caller_outputs.funcs.len() != expected_test_count - || callee_inputs.funcs.len() != expected_test_count - || callee_outputs.funcs.len() != expected_test_count - { - Err::<(), _>(RunError::TestCountMismatch( - expected_test_count, - caller_inputs.funcs.len(), - caller_outputs.funcs.len(), - callee_inputs.funcs.len(), - callee_outputs.funcs.len(), - )) - .expect("TODO"); - } // Layer 1 is the funcs/subtests. Because we have already checked // that they agree on their lengths, we can zip them together @@ -194,6 +177,7 @@ impl TestHarness { } } + #[allow(clippy::too_many_arguments)] fn check_vals( &self, key: &TestKey, @@ -239,6 +223,14 @@ impl TestHarness { expected_vals.into_iter().zip(caller_vals).zip(callee_vals) { if let Ty::Tagged(tagged_ty) = types.realize_ty(expected_val.ty) { + // This value is "fake" and is actually the semantic tag of tagged union. + // In this case showing the bytes doesn't make sense, so show the Variant name + // (although we get bytes here they're the array index into the variant, + // a purely magical value that only makes sense to the harness itself!). + // + // Also we use u32::MAX to represent a poison "i dunno what it is, but it's + // definitely not the One variant we statically expected!", so most of the + // time we're here to print and shrug. let expected_tag = expected_val.generate_idx(tagged_ty.variants.len()); let caller_tag = u32::from_ne_bytes(<[u8; 4]>::try_from(&caller_val[..4]).unwrap()) as usize; diff --git a/src/harness/mod.rs b/src/harness/mod.rs index 94a54d3..91f565b 100644 --- a/src/harness/mod.rs +++ b/src/harness/mod.rs @@ -32,7 +32,7 @@ pub fn init_dirs() -> Result> { } impl TestHarness { - fn base_id( + pub fn base_id( &self, TestKey { test, @@ -94,6 +94,10 @@ impl TestHarness { output.push_str(separator); output.push_str("print"); } + WriteImpl::Assert => { + output.push_str(separator); + output.push_str("assert"); + } WriteImpl::Noop => { output.push_str(separator); output.push_str("noop"); diff --git a/src/harness/run.rs b/src/harness/run.rs index 306b49c..db87b83 100644 --- a/src/harness/run.rs +++ b/src/harness/run.rs @@ -14,7 +14,41 @@ impl TestHarness { test_dylib: &LinkOutput, ) -> Result { let full_test_name = self.full_test_name(key); - run_dynamic_test(test_dylib, &full_test_name) + let output = run_dynamic_test(test_dylib, &full_test_name)?; + self.check_ran_all(key, &output)?; + Ok(output) + } + + fn check_ran_all( + &self, + key: &TestKey, + RunOutput { + caller_inputs, + caller_outputs, + callee_inputs, + callee_outputs, + }: &RunOutput, + ) -> Result<(), RunError> { + let test = &self.tests[&key.test]; + // As a basic sanity-check, make sure everything agrees on how + // many tests actually executed. If this fails, then something + // is very fundamentally broken and needs to be fixed. + let expected_funcs = key.options.functions.active_funcs(&test.types); + let expected_test_count = expected_funcs.len(); + if caller_inputs.funcs.len() != expected_test_count + || caller_outputs.funcs.len() != expected_test_count + || callee_inputs.funcs.len() != expected_test_count + || callee_outputs.funcs.len() != expected_test_count + { + return Err(RunError::TestCountMismatch( + expected_test_count, + caller_inputs.funcs.len(), + caller_outputs.funcs.len(), + callee_inputs.funcs.len(), + callee_outputs.funcs.len(), + )); + } + Ok(()) } } diff --git a/src/main.rs b/src/main.rs index b227396..50be1a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ mod procgen; mod report; use abis::*; -use camino::Utf8PathBuf; +use camino::{Utf8Path, Utf8PathBuf}; use error::*; use harness::*; use report::*; @@ -134,6 +134,18 @@ impl TestHarness { get_test_rules(test_key, &*caller, &*callee) } + + pub fn spawn_test( + self: Arc, + rt: &tokio::runtime::Runtime, + rules: TestRules, + test_key: TestKey, + out_dir: Utf8PathBuf, + ) -> tokio::task::JoinHandle { + let harness = self.clone(); + rt.spawn(async move { harness.do_test(test_key, rules, out_dir).await }) + } + /// Generate, Compile, Link, Load, and Run this test. pub async fn do_test( &self, @@ -299,16 +311,13 @@ fn main() -> Result<(), Box> { }, }; let rules = harness.get_test_rules(&test_key); + let task = harness.clone().spawn_test( + &rt, + rules.clone(), + test_key.clone(), + out_dir.clone(), + ); - let task = { - let runner = harness.clone(); - let rules = rules.clone(); - let test_key = test_key.clone(); - let out_dir = out_dir.clone(); - rt.spawn( - async move { runner.do_test(test_key, rules, out_dir).await }, - ) - }; // FIXME: we can make everything parallel by immediately returning // and making the following code happen in subsequent pass. For now // let's stay single-threaded to do things one step at a time. @@ -363,7 +372,73 @@ fn main() -> Result<(), Box> { } if full_report.failed() { + do_thing(&harness, &rt, &out_dir, &full_report); Err(TestsFailed {})?; } Ok(()) } + +fn do_thing( + harness: &Arc, + rt: &tokio::runtime::Runtime, + out_dir: &Utf8Path, + reports: &FullReport, +) { + eprintln!("rerunning failures"); + for report in &reports.tests { + let Some(check) = report.results.check.as_ref() else { + continue; + }; + for func_result in &check.subtest_checks { + let Err(failure) = func_result else { + continue; + }; + let functions = match *failure { + CheckFailure::ArgCountMismatch { func_idx, .. } => FunctionSelector::One { + idx: func_idx, + args: ArgSelector::All, + }, + CheckFailure::ValCountMismatch { + func_idx, arg_idx, .. + } => FunctionSelector::One { + idx: func_idx, + args: ArgSelector::One { + idx: arg_idx, + vals: ValSelector::All, + }, + }, + CheckFailure::ValMismatch { + func_idx, + arg_idx, + val_idx, + .. + } + | CheckFailure::TagMismatch { + func_idx, + arg_idx, + val_idx, + .. + } => FunctionSelector::One { + idx: func_idx, + args: ArgSelector::One { + idx: arg_idx, + vals: ValSelector::One { idx: val_idx }, + }, + }, + }; + + let mut test_key = report.key.clone(); + test_key.options.functions = functions; + test_key.options.val_writer = WriteImpl::Print; + let rules = report.rules.clone(); + eprintln!("rerunning {}", harness.base_id(&test_key, None, "::")); + let task = harness + .clone() + .spawn_test(rt, rules, test_key, out_dir.to_owned()); + let results = rt.block_on(task).expect("failed to join task"); + let source = results.source.unwrap().unwrap(); + eprintln!(" caller: {}", source.caller_src); + eprintln!(" callee: {}", source.callee_src); + } + } +}