diff --git a/kdl-script/src/parse.rs b/kdl-script/src/parse.rs index 6b26288..708a45b 100644 --- a/kdl-script/src/parse.rs +++ b/kdl-script/src/parse.rs @@ -218,11 +218,30 @@ pub enum Repr { Transparent, } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, serde::Serialize)] pub enum LangRepr { Rust, C, } +impl std::fmt::Display for LangRepr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let s = match self { + LangRepr::Rust => "rust", + LangRepr::C => "c", + }; + s.fmt(f) + } +} +impl std::str::FromStr for LangRepr { + type Err = String; + fn from_str(s: &str) -> std::result::Result { + match s { + "rust" => Ok(Self::Rust), + "c" => Ok(Self::C), + _ => Err(format!("unknown lang repr {s}")), + } + } +} /// An attribute to passthrough to the target language. /// diff --git a/src/abis/c/declare.rs b/src/abis/c/declare.rs index cf1de5f..fa356a4 100644 --- a/src/abis/c/declare.rs +++ b/src/abis/c/declare.rs @@ -220,7 +220,7 @@ impl CcAbiImpl { // Nominal types we need to emit a decl for Ty::Struct(struct_ty) => { // Emit an actual struct decl - self.generate_repr_attr(f, &struct_ty.attrs, "struct")?; + self.generate_repr_attr(f, state, &struct_ty.attrs, "struct")?; writeln!(f, "typedef struct {} {{", struct_ty.name)?; f.add_indent(1); for field in &struct_ty.fields { @@ -233,7 +233,7 @@ impl CcAbiImpl { } Ty::Union(union_ty) => { // Emit an actual union decl - self.generate_repr_attr(f, &union_ty.attrs, "union")?; + self.generate_repr_attr(f, state, &union_ty.attrs, "union")?; writeln!(f, "typedef union {} {{", union_ty.name)?; f.add_indent(1); for field in &union_ty.fields { @@ -246,7 +246,7 @@ impl CcAbiImpl { } Ty::Enum(enum_ty) => { // Emit an actual enum decl - self.generate_repr_attr(f, &enum_ty.attrs, "enum")?; + self.generate_repr_attr(f, state, &enum_ty.attrs, "enum")?; writeln!(f, "typedef enum {} {{", enum_ty.name)?; f.add_indent(1); for variant in &enum_ty.variants { @@ -336,88 +336,80 @@ impl CcAbiImpl { pub fn generate_repr_attr( &self, - _f: &mut Fivemat, + f: &mut Fivemat, + state: &TestState, attrs: &[Attr], _ty_style: &str, ) -> Result<(), GenerateError> { + use kdl_script::parse::{AttrAligned, AttrPacked, AttrPassthrough, AttrRepr, Repr}; if !attrs.is_empty() { return Err(UnsupportedError::Other( "c doesn't support attrs yet".to_owned(), ))?; } - /* - let mut default_c_repr = true; + let mut default_lang_repr = true; + let mut lang_repr = None; let mut repr_attrs = vec![]; let mut other_attrs = vec![]; for attr in attrs { match attr { - Attr::Align(AttrAligned { align }) => { - repr_attrs.push(format!("align({})", align.val)); + Attr::Align(AttrAligned { align: _ }) => { + return Err(UnsupportedError::Other("@align not implemented".to_owned()))?; } Attr::Packed(AttrPacked {}) => { - repr_attrs.push("packed".to_owned()); + return Err(UnsupportedError::Other( + "@packed not implemented".to_owned(), + ))?; } Attr::Passthrough(AttrPassthrough(attr)) => { - other_attrs.push(attr.to_string()); + other_attrs.push(attr); } Attr::Repr(AttrRepr { reprs }) => { + default_lang_repr = false; // Any explicit repr attributes disables default C - default_c_repr = false; for repr in reprs { - let val = match repr { - Repr::Primitive(prim) => match prim { - PrimitiveTy::I8 => "i8", - PrimitiveTy::I16 => "i16", - PrimitiveTy::I32 => "i32", - PrimitiveTy::I64 => "i64", - PrimitiveTy::I128 => "i128", - PrimitiveTy::U8 => "u8", - PrimitiveTy::U16 => "u16", - PrimitiveTy::U32 => "u32", - PrimitiveTy::U64 => "u64", - PrimitiveTy::U128 => "u128", - PrimitiveTy::I256 - | PrimitiveTy::U256 - | PrimitiveTy::F16 - | PrimitiveTy::F32 - | PrimitiveTy::F64 - | PrimitiveTy::F128 - | PrimitiveTy::Bool - | PrimitiveTy::Ptr => { + match repr { + Repr::Transparent => { + return Err(UnsupportedError::Other( + "unsupport repr transparent".to_owned(), + ))?; + } + Repr::Primitive(prim) => { + return Err(UnsupportedError::Other(format!( + "unsupport repr {prim:?}" + )))?; + } + Repr::Lang(repr) => { + if let Some(old_repr) = lang_repr { return Err(UnsupportedError::Other(format!( - "unsupport repr({prim:?})" + "multiple lang reprs on one type ({old_repr}, {repr})" )))?; } - }, - Repr::Lang(LangRepr::C) => "C", - Repr::Lang(LangRepr::Rust) => { + lang_repr = Some(*repr); continue; } - Repr::Transparent => "transparent", }; - repr_attrs.push(val.to_owned()); } } } } - if default_c_repr { - repr_attrs.push("C".to_owned()); + if default_lang_repr && lang_repr.is_none() { + lang_repr = Some(state.options.repr); } - write!(f, "#[repr(")?; - let mut multi = false; - for repr in repr_attrs { - if multi { - write!(f, ", ")?; + if let Some(lang_repr) = lang_repr { + if let Some(attr) = self.lang_repr_decl(lang_repr)? { + repr_attrs.push(attr.to_owned()); } - multi = true; - write!(f, "{repr}")?; } - writeln!(f, ")]")?; + if !repr_attrs.is_empty() { + return Err(UnsupportedError::Other( + "c doesn't implement non-trivial reprs attributes yet".to_owned(), + ))?; + } for attr in other_attrs { writeln!(f, "{}", attr)?; } - */ Ok(()) } @@ -478,6 +470,10 @@ impl CcAbiImpl { // all properly convered by other ABIs return Err(self.unsupported_convention(&convention))?; } + // C knows no Rust + Rust => { + return Err(self.unsupported_convention(&convention))?; + } C => "", Cdecl => { if self.platform == Windows { @@ -524,6 +520,15 @@ impl CcAbiImpl { Ok(val) } + fn lang_repr_decl(&self, repr: LangRepr) -> Result, GenerateError> { + match repr { + LangRepr::Rust => Err(UnsupportedError::Other( + "c doesn't support repr rust".to_owned(), + ))?, + LangRepr::C => Ok(None), + } + } + fn unsupported_convention(&self, convention: &CallingConvention) -> UnsupportedError { UnsupportedError::Other(format!("unsupported convention {convention}")) } diff --git a/src/abis/mod.rs b/src/abis/mod.rs index 23da20c..2f1d7af 100644 --- a/src/abis/mod.rs +++ b/src/abis/mod.rs @@ -9,6 +9,7 @@ use std::{collections::HashMap, fmt::Write, sync::Arc}; use camino::Utf8Path; use kdl_script::{ + parse::LangRepr, types::{FuncIdx, TyIdx}, DefinitionGraph, PunEnv, TypedProgram, }; @@ -30,17 +31,21 @@ pub static ABI_IMPL_CLANG: &str = "clang"; pub static ABI_IMPL_MSVC: &str = "msvc"; pub static ALL_CONVENTIONS: &[CallingConvention] = &[ + // C! CallingConvention::C, CallingConvention::Cdecl, CallingConvention::Stdcall, CallingConvention::Fastcall, CallingConvention::Vectorcall, + // Rust! + CallingConvention::Rust, // Note sure if these have a purpose, so omitting them for now // CallingConvention::System, // CallingConvention::Win64, // CallingConvention::Sysv64, // CallingConvention::Aapcs, ]; +pub static ALL_REPRS: &[LangRepr] = &[LangRepr::Rust, LangRepr::C]; /// A test case, fully abstract. /// @@ -95,6 +100,7 @@ pub struct TestOptions { pub functions: FunctionSelector, pub val_writer: WriteImpl, pub val_generator: ValueGeneratorKind, + pub repr: LangRepr, } impl FunctionSelector { pub fn should_write_arg(&self, func_idx: usize, arg_idx: usize) -> bool { @@ -333,6 +339,8 @@ impl TestWithAbi { pub enum CallingConvention { /// The platform's default C convention (cdecl?) C, + /// Rust's default calling convention + Rust, /// ??? Cdecl, /// The platorm's default OS convention (usually C, but Windows is Weird). @@ -361,6 +369,7 @@ impl CallingConvention { pub fn name(&self) -> &'static str { match self { CallingConvention::C => "c", + CallingConvention::Rust => "rust", CallingConvention::Cdecl => "cdecl", CallingConvention::System => "system", CallingConvention::Win64 => "win64", @@ -385,6 +394,7 @@ impl std::str::FromStr for CallingConvention { fn from_str(s: &str) -> Result { let val = match s { "c" => CallingConvention::C, + "rust" => CallingConvention::Rust, "cdecl" => CallingConvention::Cdecl, "system" => CallingConvention::System, "win64" => CallingConvention::Win64, diff --git a/src/abis/rust/declare.rs b/src/abis/rust/declare.rs index 6255515..0760504 100644 --- a/src/abis/rust/declare.rs +++ b/src/abis/rust/declare.rs @@ -163,7 +163,7 @@ impl RustcAbiImpl { // Nominal types we need to emit a decl for Ty::Struct(struct_ty) => { // Emit an actual struct decl - self.generate_repr_attr(f, &struct_ty.attrs, "struct")?; + self.generate_repr_attr(f, state, &struct_ty.attrs, "struct")?; if has_borrows { writeln!(f, "struct {}<'a> {{", struct_ty.name)?; } else { @@ -184,7 +184,7 @@ impl RustcAbiImpl { } Ty::Union(union_ty) => { // Emit an actual union decl - self.generate_repr_attr(f, &union_ty.attrs, "union")?; + self.generate_repr_attr(f, state, &union_ty.attrs, "union")?; if has_borrows { writeln!(f, "union {}<'a> {{", union_ty.name)?; } else { @@ -205,7 +205,7 @@ impl RustcAbiImpl { } Ty::Enum(enum_ty) => { // Emit an actual enum decl - self.generate_repr_attr(f, &enum_ty.attrs, "enum")?; + self.generate_repr_attr(f, state, &enum_ty.attrs, "enum")?; writeln!(f, "#[derive(Debug, Copy, Clone, PartialEq)]")?; writeln!(f, "enum {} {{", enum_ty.name)?; f.add_indent(1); @@ -218,7 +218,7 @@ impl RustcAbiImpl { } Ty::Tagged(tagged_ty) => { // Emit an actual enum decl - self.generate_repr_attr(f, &tagged_ty.attrs, "tagged")?; + self.generate_repr_attr(f, state, &tagged_ty.attrs, "tagged")?; if has_borrows { writeln!(f, "enum {}<'a> {{", tagged_ty.name)?; } else { @@ -306,10 +306,12 @@ impl RustcAbiImpl { pub fn generate_repr_attr( &self, f: &mut Fivemat, + state: &TestState, attrs: &[Attr], _ty_style: &str, ) -> Result<(), GenerateError> { - let mut default_c_repr = true; + let mut default_lang_repr = true; + let mut lang_repr = None; let mut repr_attrs = vec![]; let mut other_attrs = vec![]; for attr in attrs { @@ -324,8 +326,8 @@ impl RustcAbiImpl { other_attrs.push(attr.to_string()); } Attr::Repr(AttrRepr { reprs }) => { + default_lang_repr = false; // Any explicit repr attributes disables default C - default_c_repr = false; for repr in reprs { let val = match repr { Repr::Primitive(prim) => match prim { @@ -352,8 +354,13 @@ impl RustcAbiImpl { )))?; } }, - Repr::Lang(LangRepr::C) => "C", - Repr::Lang(LangRepr::Rust) => { + Repr::Lang(repr) => { + if let Some(old_repr) = lang_repr { + return Err(UnsupportedError::Other(format!( + "multiple lang reprs on one type ({old_repr}, {repr})" + )))?; + } + lang_repr = Some(*repr); continue; } Repr::Transparent => "transparent", @@ -363,19 +370,26 @@ impl RustcAbiImpl { } } } - if default_c_repr { - repr_attrs.push("C".to_owned()); + if default_lang_repr && lang_repr.is_none() { + lang_repr = Some(state.options.repr); } - write!(f, "#[repr(")?; - let mut multi = false; - for repr in repr_attrs { - if multi { - write!(f, ", ")?; + if let Some(lang_repr) = lang_repr { + if let Some(attr) = self.lang_repr_decl(lang_repr)? { + repr_attrs.push(attr.to_owned()); } - multi = true; - write!(f, "{repr}")?; } - writeln!(f, ")]")?; + if !repr_attrs.is_empty() { + write!(f, "#[repr(")?; + let mut multi = false; + for repr in repr_attrs { + if multi { + write!(f, ", ")?; + } + multi = true; + write!(f, "{repr}")?; + } + writeln!(f, ")]")?; + } for attr in other_attrs { writeln!(f, "{}", attr)?; } @@ -413,6 +427,14 @@ impl RustcAbiImpl { Ok(()) } + pub fn lang_repr_decl(&self, repr: LangRepr) -> Result, GenerateError> { + let s = match repr { + LangRepr::Rust => None, + LangRepr::C => Some("C"), + }; + Ok(s) + } + pub fn convention_decl( &self, convention: CallingConvention, @@ -421,6 +443,7 @@ impl RustcAbiImpl { let conv = match convention { CallingConvention::C => "C", + CallingConvention::Rust => "Rust", CallingConvention::System => "system", CallingConvention::Win64 => "win64", CallingConvention::Sysv64 => "sysv64", diff --git a/src/cli.rs b/src/cli.rs index fd290f4..638d9ce 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,6 +1,7 @@ use crate::{abis::*, files::Paths, Config, OutputFormat}; use camino::Utf8PathBuf; use clap::Parser; +use kdl_script::parse::LangRepr; use tracing::warn; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; use vals::ValueGeneratorKind; @@ -12,6 +13,8 @@ struct Cli { #[clap(long)] conventions: Vec, #[clap(long)] + reprs: Vec, + #[clap(long)] impls: Vec, #[clap(long)] pairs: Vec, @@ -45,6 +48,11 @@ pub fn make_app() -> Config { } else { config.conventions }; + let run_reprs = if config.reprs.is_empty() { + ALL_REPRS.to_vec() + } else { + config.reprs + }; let run_impls = config.impls; @@ -115,6 +123,7 @@ Hint: Try using `--pairs {name}_calls_rustc` or `--pairs rustc_calls_{name}`. output_format, procgen_tests, run_conventions, + run_reprs, run_impls, run_tests, run_pairs, diff --git a/src/harness/mod.rs b/src/harness/mod.rs index 20df7aa..00e699b 100644 --- a/src/harness/mod.rs +++ b/src/harness/mod.rs @@ -48,7 +48,7 @@ impl TestHarness { tests_with_abi_impl: Default::default(), generated_sources: Default::default(), built_static_libs: Default::default(), - concurrency_limiter: Semaphore::new(8), + concurrency_limiter: Semaphore::new(128), } } pub fn add_abi_impl(&mut self, id: AbiImplId, abi_impl: A) { @@ -216,6 +216,7 @@ impl TestHarness { functions, val_writer, val_generator, + repr, }, caller, callee, @@ -223,7 +224,12 @@ impl TestHarness { call_side: Option, separator: &str, ) -> String { - let mut output = format!("{test}{separator}{convention}"); + let mut output = String::new(); + output.push_str(test); + output.push_str(separator); + output.push_str(&format!("conv_{convention}")); + output.push_str(separator); + output.push_str(&format!("repr_{repr}")); if let FunctionSelector::One { idx, args } = functions { let test = self.tests[test].clone(); let func = test.types.realize_func(*idx); diff --git a/src/main.rs b/src/main.rs index af56822..d8fb459 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,7 @@ use abis::*; use error::*; use files::Paths; use harness::*; +use kdl_script::parse::LangRepr; use report::*; use std::error::Error; use std::process::Command; @@ -65,6 +66,7 @@ pub struct Config { pub output_format: OutputFormat, pub procgen_tests: bool, pub run_conventions: Vec, + pub run_reprs: Vec, pub run_impls: Vec, pub run_pairs: Vec<(String, String)>, pub run_tests: Vec, @@ -150,36 +152,40 @@ fn main() -> Result<(), Box> { // Don't bother with a convention if the test doesn't use it. return Vec::new(); } - // Create versions of the test for each "X calls Y" pair we care about. - cfg.run_pairs + cfg.run_reprs .iter() - .filter_map(|(caller_id, callee_id)| { - if !cfg.run_impls.is_empty() - && !cfg.run_impls.iter().any(|x| x == caller_id) - && !cfg.run_impls.iter().any(|x| &**x == callee_id) - { - return None; - } + .flat_map(|repr| { + // Create versions of the test for each "X calls Y" pair we care about. + cfg.run_pairs.iter().filter_map(|(caller_id, callee_id)| { + if !cfg.run_impls.is_empty() + && !cfg.run_impls.iter().any(|x| x == caller_id) + && !cfg.run_impls.iter().any(|x| &**x == callee_id) + { + return None; + } - // Run the test! - let test_key = TestKey { - test: test.name.to_owned(), - caller: caller_id.to_owned(), - callee: callee_id.to_owned(), - options: TestOptions { - convention: *convention, - functions: FunctionSelector::All, - val_writer: cfg.write_impl, - val_generator: cfg.val_generator, - }, - }; - let rules = harness.get_test_rules(&test_key); - let task = - harness - .clone() - .spawn_test(&rt, rules.clone(), test_key.clone()); + // Run the test! + let test_key = TestKey { + test: test.name.to_owned(), + caller: caller_id.to_owned(), + callee: callee_id.to_owned(), + options: TestOptions { + convention: *convention, + repr: *repr, + functions: FunctionSelector::All, + val_writer: cfg.write_impl, + val_generator: cfg.val_generator, + }, + }; + let rules = harness.get_test_rules(&test_key); + let task = harness.clone().spawn_test( + &rt, + rules.clone(), + test_key.clone(), + ); - Some(task) + Some(task) + }) }) .collect() })