Repository: Gankra/abi-cafe (language: rust)

Issue:
make unspecified-repr be another combinatoric lever?
Without a specified calling convention, we generate a test for every possible calling convention. We could *also* do the same thing for type repr (C, Rust, u8, ...). (Right now unspecified => repr C)

If we want to do this, it would have to become a new member of TestOptions, and therefore a part of every TestKey 

https://github.com/Gankra/abi-cafe/blob/6ba68656bd74d05692e3abb83a51a0bfc2b7ce32/src/abis/mod.rs#L90-L98

I think the absolute amount of work involved isn't perse a ton, and I hopefully setup the code such that adding a new TestOptions field will give you compiler errors everywhere you need to deal with it.

That said it's a lot of tedious threading of options and has some weird decisions to make around "how do you prevent repr(rust) types from getting shoved into `extern "C"` interfaces (or do you?).


Discussion / hints:

Fix patch:
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<Self, Self::Err> {
+        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<Option<&'static str>, 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<Self, Self::Err> {
         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<Option<&'static str>, 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<CallingConvention>,
     #[clap(long)]
+    reprs: Vec<LangRepr>,
+    #[clap(long)]
     impls: Vec<String>,
     #[clap(long)]
     pairs: Vec<String>,
@@ -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<A: AbiImpl + Send + Sync + 'static>(&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<CallSide>,
         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<CallingConvention>,
+    pub run_reprs: Vec<LangRepr>,
     pub run_impls: Vec<String>,
     pub run_pairs: Vec<(String, String)>,
     pub run_tests: Vec<String>,
@@ -150,36 +152,40 @@ fn main() -> Result<(), Box<dyn Error>> {
                         // 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()
                 })


Test patch: