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

Issue:
Test for `f128`
Is a test for `f128` possible? To check GCC `__float128` against LLVM's `fp128`


Discussion / hints:
Oh rad, thank you for letting me know about these!

Apologies for the radio silence, I just finished rewriting all of abi-cafe to make adding new stuff way easier >.>;
That is awesome! We have both f16 and f128 available unstably which could be checked against `_Float16` and `_Float128`/`__float128`. These come with a handful of known ABI issues already https://github.com/rust-lang/rust/issues/116909#issuecomment-1969554030

Any chance you are planning to upstream this in to Rust's CI somehow? I know there has been a lot of interest, just nobody has figured out how to do the work.
Notes on adding support for these types:

First off, the type system already contains them, everything just refuses to implement them.

# Adding to rust

There are two parts to support: teaching the rust backend to generate the values, and teaching it to detect that the test uses f128/f16, and emitting a feature gate/nightly check, similar to vectorcall.

## Teaching to use the values/types

https://github.com/Gankra/abi-cafe/blob/6ba68656bd74d05692e3abb83a51a0bfc2b7ce32/src/abis/rust/declare.rs#L79-L92

https://github.com/Gankra/abi-cafe/blob/6ba68656bd74d05692e3abb83a51a0bfc2b7ce32/src/abis/rust/init.rs#L34-L49

## Detecting the feature

Kinda spaghetti for vectorcall here. Detecting f16/f128 usage is also a bit harder.

https://github.com/Gankra/abi-cafe/blob/6ba68656bd74d05692e3abb83a51a0bfc2b7ce32/src/abis/rust/write.rs#L18-L20

https://github.com/Gankra/abi-cafe/blob/6ba68656bd74d05692e3abb83a51a0bfc2b7ce32/src/abis/rust/declare.rs#L451-L456

You could maybe just do a prepass on the definitions list to sniff for those types?

https://github.com/Gankra/abi-cafe/blob/6ba68656bd74d05692e3abb83a51a0bfc2b7ce32/src/abis/rust/declare.rs#L30-L40


# Adding to C

Essentially the exact same thing as Rust, but less "detecting nightly and setting feature-gates" and more "detecting C toolchain, emitting toolchain-specific code". See for example where convention attributes get processed:

https://github.com/Gankra/abi-cafe/blob/6ba68656bd74d05692e3abb83a51a0bfc2b7ce32/src/abis/c/declare.rs#L481-L492


Fix patch:
diff --git a/src/report.rs b/src/report.rs
index 33a9254..9422e8c 100644
--- a/src/report.rs
+++ b/src/report.rs
@@ -34,6 +34,11 @@ pub fn get_test_rules(test: &TestKey, caller: &dyn Toolchain, callee: &dyn Toolc
         result.check = Random;
     }
 
+    // CI GCC is too old to support `_Float16`.
+    if cfg!(all(target_arch = "x86_64", target_os = "linux")) && is_c && test.test == "f16" {
+        result.check = Random;
+    }
+
     // FIXME: investigate why this is failing to build
     if cfg!(windows) && is_c && (test.test == "EmptyStruct" || test.test == "EmptyStructInside") {
         result.check = Busted(Build);
diff --git a/src/toolchains/c.rs b/src/toolchains/c.rs
index d8eaf06..08d80f2 100644
--- a/src/toolchains/c.rs
+++ b/src/toolchains/c.rs
@@ -270,15 +270,23 @@ impl CcToolchain {
 
 impl CcToolchain {
     pub fn new(_system_info: &Config, mode: &'static str) -> Self {
-        let compiler = cc::Build::new().get_compiler();
-        let cc_flavor = if compiler.is_like_msvc() {
-            CCFlavor::Msvc
-        } else if compiler.is_like_gnu() {
-            CCFlavor::Gcc
-        } else if compiler.is_like_clang() {
-            CCFlavor::Clang
-        } else {
-            panic!("Unknown compiler flavour for CC");
+        let cc_flavor = match mode {
+            TOOLCHAIN_GCC => CCFlavor::Gcc,
+            TOOLCHAIN_CLANG => CCFlavor::Clang,
+            TOOLCHAIN_MSVC => CCFlavor::Msvc,
+            TOOLCHAIN_CC => {
+                let compiler = cc::Build::new().get_compiler();
+                if compiler.is_like_msvc() {
+                    CCFlavor::Msvc
+                } else if compiler.is_like_gnu() {
+                    CCFlavor::Gcc
+                } else if compiler.is_like_clang() {
+                    CCFlavor::Clang
+                } else {
+                    panic!("Unknown compiler flavour for CC");
+                }
+            }
+            mode => panic!("Unknown CcToolchain mode {mode:?}"),
         };
 
         let platform = if cfg!(target_os = "windows") {
@@ -294,6 +302,16 @@ impl CcToolchain {
         }
     }
 
+    fn extra_flags(&self) -> &[&str] {
+        match self.cc_flavor {
+            CCFlavor::Gcc if cfg!(target_arch = "arm") => &["-mfp16-format=ieee"],
+            CCFlavor::Clang if cfg!(all(target_arch = "powerpc64", target_endian = "little")) => {
+                &["-mfloat128"]
+            }
+            _ => &[],
+        }
+    }
+
     fn compile_cc(
         &self,
         src_path: &Utf8Path,
@@ -307,7 +325,11 @@ impl CcToolchain {
         info!("out: {}", out_dir);
         info!("lib: {}", lib_name);
          */
-        cc::Build::new()
+        let mut build = cc::Build::new();
+        for flag in self.extra_flags() {
+            build.flag(flag);
+        }
+        build
             .file(src_path)
             .opt_level(0)
             .cargo_metadata(false)
@@ -326,8 +348,11 @@ impl CcToolchain {
     ) -> Result<String, BuildError> {
         let obj_path = out_dir.join(format!("{lib_name}.o"));
         let lib_path = out_dir.join(format!("lib{lib_name}.a"));
-        Command::new("clang")
-            .arg("-ffunction-sections")
+        let mut cmd = Command::new("clang");
+        for flag in self.extra_flags() {
+            cmd.arg(flag);
+        }
+        cmd.arg("-ffunction-sections")
             .arg("-fdata-sections")
             .arg("-fPIC")
             .arg("-o")
@@ -354,8 +379,11 @@ impl CcToolchain {
     ) -> Result<String, BuildError> {
         let obj_path = out_dir.join(format!("{lib_name}.o"));
         let lib_path = out_dir.join(format!("lib{lib_name}.a"));
-        Command::new("gcc")
-            .arg("-ffunction-sections")
+        let mut cmd = Command::new("gcc");
+        for flag in self.extra_flags() {
+            cmd.arg(flag);
+        }
+        cmd.arg("-ffunction-sections")
             .arg("-fdata-sections")
             .arg("-fPIC")
             .arg("-o")
diff --git a/src/toolchains/c/declare.rs b/src/toolchains/c/declare.rs
index ea6d909..697de2b 100644
--- a/src/toolchains/c/declare.rs
+++ b/src/toolchains/c/declare.rs
@@ -78,11 +78,87 @@ impl CcToolchain {
                     PrimitiveTy::U256 => {
                         Err(UnsupportedError::Other("c doesn't have u256?".to_owned()))?
                     }
-                    PrimitiveTy::F16 => {
-                        Err(UnsupportedError::Other("c doesn't have f16?".to_owned()))?
-                    }
+                    PrimitiveTy::F16 => match &self.cc_flavor {
+                        CCFlavor::Gcc
+                            if cfg!(any(
+                                target_arch = "x86",
+                                target_arch = "x86_64",
+                                target_arch = "arm",
+                                target_arch = "aarch64",
+                                target_arch = "riscv32",
+                                target_arch = "riscv64",
+                            )) =>
+                        {
+                            "_Float16 "
+                        }
+                        CCFlavor::Gcc => Err(UnsupportedError::Other(
+                            "GCC isn't known to support f16 on this target".to_owned(),
+                        ))?,
+                        CCFlavor::Clang
+                            if cfg!(any(
+                                all(target_arch = "x86", target_feature = "sse2"),
+                                target_arch = "x86_64",
+                                target_arch = "arm",
+                                target_arch = "aarch64",
+                                target_arch = "riscv32",
+                                target_arch = "riscv64",
+                            )) =>
+                        {
+                            "_Float16 "
+                        }
+                        CCFlavor::Clang => Err(UnsupportedError::Other(
+                            "Clang isn't known to support f16 on this target".to_owned(),
+                        ))?,
+                        CCFlavor::Msvc => Err(UnsupportedError::Other(
+                            "MSVC doesn't support f16".to_owned(),
+                        ))?,
+                    },
                     PrimitiveTy::F128 => {
-                        Err(UnsupportedError::Other("c doesn't have f128?".to_owned()))?
+                        match &self.cc_flavor {
+                            CCFlavor::Gcc
+                                if cfg!(any(
+                                    target_arch = "x86",
+                                    target_arch = "x86_64",
+                                    target_arch = "aarch64",
+                                    target_arch = "riscv32",
+                                    target_arch = "riscv64",
+                                    target_arch = "loongarch64",
+                                    // GCC PowerPC support requires the VSX feature, which is only
+                                    // enabled by default on powerpc64le. Rust doesn't currently support
+                                    // `cfg(target_feature = "vsx").
+                                    all(target_arch = "powerpc64", target_endian = "little"),
+                                )) && !cfg!(target_vendor = "apple") =>
+                            {
+                                "_Float128 "
+                            }
+                            CCFlavor::Gcc => Err(UnsupportedError::Other(
+                                "GCC isn't known to support f128 on this target".to_owned(),
+                            ))?,
+                            CCFlavor::Clang
+                                if cfg!(any(
+                                    target_arch = "x86",
+                                    target_arch = "x86_64",
+                                    target_arch = "aarch64",
+                                    target_arch = "riscv32",
+                                    target_arch = "riscv64",
+                                    // Clang PowerPC support requires the VSX feature, which is only
+                                    // enabled by default on powerpc64le. Rust doesn't currently support
+                                    // `cfg(target_feature = "vsx").
+                                    all(target_arch = "powerpc64", target_endian = "little"),
+                                )) && !cfg!(any(
+                                    target_env = "msvc",
+                                    target_vendor = "apple",
+                                )) =>
+                            {
+                                "__float128 "
+                            }
+                            CCFlavor::Clang => Err(UnsupportedError::Other(
+                                "Clang isn't known to support f128 on this target".to_owned(),
+                            ))?,
+                            CCFlavor::Msvc => Err(UnsupportedError::Other(
+                                "MSVC doesn't support f128".to_owned(),
+                            ))?,
+                        }
                     }
                 };
                 (name.to_owned(), None)
diff --git a/src/toolchains/c/init.rs b/src/toolchains/c/init.rs
index b5817a7..c52c2db 100644
--- a/src/toolchains/c/init.rs
+++ b/src/toolchains/c/init.rs
@@ -72,9 +72,19 @@ impl CcToolchain {
                 PrimitiveTy::U256 => {
                     Err(UnsupportedError::Other("c doesn't have u256?".to_owned()))?
                 }
-                PrimitiveTy::F16 => Err(UnsupportedError::Other("c doesn't have f16?".to_owned()))?,
+                PrimitiveTy::F16 => write!(
+                    f,
+                    "(((union {{ uint16_t bits; _Float16 value; }}){{ .bits = {} }}).value)",
+                    val.generate_u16()
+                )?,
                 PrimitiveTy::F128 => {
-                    Err(UnsupportedError::Other("c doesn't have f128?".to_owned()))?
+                    let val = val.generate_u128();
+                    let lower = val & 0x0000_0000_0000_0000_FFFF_FFFF_FFFF_FFFF;
+                    let higher = (val & 0xFFFF_FFFF_FFFF_FFFF_0000_0000_0000_0000) >> 64;
+                    write!(
+                        f,
+                        "(((union {{ __uint128_t bits; __float128 value; }}){{ .bits = ((__uint128_t){lower:#X}ull) | (((__uint128_t){higher:#X}ull) << 64) }}).value)"
+                    )?
                 }
             },
             Ty::Enum(enum_ty) => {
diff --git a/src/toolchains/rust/declare.rs b/src/toolchains/rust/declare.rs
index b39dac2..28d70f7 100644
--- a/src/toolchains/rust/declare.rs
+++ b/src/toolchains/rust/declare.rs
@@ -85,10 +85,22 @@ impl RustcToolchain {
                         Err(UnsupportedError::Other("rust doesn't have u256".to_owned()))?
                     }
                     PrimitiveTy::F16 => {
-                        Err(UnsupportedError::Other("rust doesn't have f16".to_owned()))?
+                        if self.is_nightly {
+                            "f16"
+                        } else {
+                            return Err(UnsupportedError::Other(
+                                "f16 is an unstable rust feature, requires nightly".to_owned(),
+                            ))?;
+                        }
                     }
                     PrimitiveTy::F128 => {
-                        Err(UnsupportedError::Other("rust doesn't have f128".to_owned()))?
+                        if self.is_nightly {
+                            "f128"
+                        } else {
+                            return Err(UnsupportedError::Other(
+                                "f128 is an unstable rust feature, requires nightly".to_owned(),
+                            ))?;
+                        }
                     }
                 };
                 (name.to_owned(), None)
diff --git a/src/toolchains/rust/init.rs b/src/toolchains/rust/init.rs
index 1c014f1..f8a078c 100644
--- a/src/toolchains/rust/init.rs
+++ b/src/toolchains/rust/init.rs
@@ -42,12 +42,8 @@ impl RustcToolchain {
                 PrimitiveTy::U256 => {
                     Err(UnsupportedError::Other("rust doesn't have u256".to_owned()))?
                 }
-                PrimitiveTy::F16 => {
-                    Err(UnsupportedError::Other("rust doesn't have f16".to_owned()))?
-                }
-                PrimitiveTy::F128 => {
-                    Err(UnsupportedError::Other("rust doesn't have f128".to_owned()))?
-                }
+                PrimitiveTy::F16 => write!(f, "f16::from_bits({})", val.generate_u16())?,
+                PrimitiveTy::F128 => write!(f, "f128::from_bits({})", val.generate_u128())?,
             },
             Ty::Enum(enum_ty) => {
                 let name = alias.unwrap_or(&enum_ty.name);
diff --git a/src/toolchains/rust/write.rs b/src/toolchains/rust/write.rs
index 04cad89..a15a309 100644
--- a/src/toolchains/rust/write.rs
+++ b/src/toolchains/rust/write.rs
@@ -1,6 +1,6 @@
 use super::*;
 use crate::harness::vals::Value;
-use kdl_script::types::{Ty, TyIdx};
+use kdl_script::types::{PrimitiveTy, Ty, TyIdx};
 use std::fmt::Write;
 
 impl RustcToolchain {
@@ -18,6 +18,26 @@ impl RustcToolchain {
         if state.options.convention == CallingConvention::Vectorcall {
             writeln!(f, "#![feature(abi_vectorcall)]")?;
         }
+        let mut has_f16 = false;
+        let mut has_f128 = false;
+        for def in state.defs.definitions(state.desired_funcs.iter().copied()) {
+            match def {
+                kdl_script::Definition::DeclareTy(ty) | kdl_script::Definition::DefineTy(ty) => {
+                    match state.types.realize_ty(ty) {
+                        Ty::Primitive(PrimitiveTy::F16) => has_f16 = true,
+                        Ty::Primitive(PrimitiveTy::F128) => has_f128 = true,
+                        _ => {}
+                    }
+                }
+                kdl_script::Definition::DefineFunc(_) | kdl_script::Definition::DeclareFunc(_) => {}
+            }
+        }
+        if has_f16 {
+            writeln!(f, "#![feature(f16)]")?;
+        }
+        if has_f128 {
+            writeln!(f, "#![feature(f128)]")?;
+        }
         // Load test harness "headers"
         writeln!(
             f,


Test patch:
diff --git a/include/tests/procgen/primitive/f128.procgen.kdl b/include/tests/procgen/primitive/f128.procgen.kdl
new file mode 100644
index 0000000..e69de29
diff --git a/include/tests/procgen/primitive/f16.procgen.kdl b/include/tests/procgen/primitive/f16.procgen.kdl
new file mode 100644
index 0000000..e69de29