fn_ptr/
abi_value.rs

1use core::{
2    fmt::{self, Debug, Display},
3    str::FromStr,
4};
5
6/// The abi or calling convention of a function pointer.
7#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
8// from https://github.com/rust-lang/rust/blob/4fa80a5e733e2202d7ca4c203c2fdfda41cfe7dc/compiler/rustc_abi/src/extern_abi.rs#L21
9pub enum AbiValue {
10    /* universal */
11    /// This is the same as `extern fn foo()`; whatever the default your C compiler supports.
12    C {
13        /// Whether unwinding across this abi boundary is allowed (`*-unwind`).
14        unwind: bool,
15    },
16    /// Usually the same as [`extern "C"`](AbiValue::C), except on Win32, in which case it's
17    /// [`"stdcall"`](AbiValue::Stdcall), or what you should use to link to the Windows API itself.
18    System {
19        /// Whether unwinding across this abi boundary is allowed (`*-unwind`).
20        unwind: bool,
21    },
22
23    /// The default abi when you write a normal `fn foo()` in any Rust code.
24    Rust,
25
26    /* arm */
27    /// The default for ARM.
28    Aapcs {
29        /// Whether unwinding across this abi boundary is allowed (`*-unwind`).
30        unwind: bool,
31    },
32
33    /* x86 */
34    /// The default for `x86_32` C code.
35    Cdecl {
36        /// Whether unwinding across this abi boundary is allowed (`*-unwind`).
37        unwind: bool,
38    },
39    /// The default for the Win32 API on `x86_32`.
40    Stdcall {
41        /// Whether unwinding across this abi boundary is allowed (`*-unwind`).
42        unwind: bool,
43    },
44    /// The `fastcall` abi.
45    Fastcall {
46        /// Whether unwinding across this abi boundary is allowed (`*-unwind`).
47        unwind: bool,
48    },
49    /// The Windows C++ abi.
50    Thiscall {
51        /// Whether unwinding across this abi boundary is allowed (`*-unwind`).
52        unwind: bool,
53    },
54    /// The `vectorcall` abi.
55    Vectorcall {
56        /// Whether unwinding across this abi boundary is allowed (`*-unwind`).
57        unwind: bool,
58    },
59
60    /* x86_64 */
61    /// The default for C code on non-Windows `x86_64`.
62    SysV64 {
63        /// Whether unwinding across this abi boundary is allowed (`*-unwind`).
64        unwind: bool,
65    },
66    /// The default for C code on `x86_64` Windows.
67    Win64 {
68        /// Whether unwinding across this abi boundary is allowed (`*-unwind`).
69        unwind: bool,
70    },
71
72    /* Other */
73    /// UEFI ABI, usually an alias of C, but sometimes an arch-specific alias.
74    EfiApi,
75}
76
77impl AbiValue {
78    /// Returns whether unwinding after a panic is allowed inside the called function.
79    #[must_use]
80    pub const fn allows_unwind(&self) -> bool {
81        match *self {
82            AbiValue::Rust => true,
83            AbiValue::EfiApi => false,
84            AbiValue::C { unwind }
85            | AbiValue::System { unwind }
86            | AbiValue::Aapcs { unwind }
87            | AbiValue::Cdecl { unwind }
88            | AbiValue::Stdcall { unwind }
89            | AbiValue::Fastcall { unwind }
90            | AbiValue::Thiscall { unwind }
91            | AbiValue::Vectorcall { unwind }
92            | AbiValue::SysV64 { unwind }
93            | AbiValue::Win64 { unwind } => unwind,
94        }
95    }
96
97    /// Canonicalize this abi for the current target.
98    ///
99    /// Maps aliases (e.g. `system`, `cdecl`) to the concrete abi actually used on
100    /// the current OS/architecture, following Rust compiler rules.
101    ///
102    /// Returns [`None`] if this abi is not supported on the current target.
103    #[must_use]
104    pub fn canonize(self, has_c_varargs: bool) -> Option<AbiValue> {
105        // from https://github.com/rust-lang/rust/blob/4fa80a5e733e2202d7ca4c203c2fdfda41cfe7dc/compiler/rustc_target/src/spec/abi_map.rs#L79
106        let os_windows = cfg!(target_os = "windows");
107        let os_vexos = cfg!(target_os = "vexos");
108
109        let arch_x86 = cfg!(target_arch = "x86");
110        let arch_x86_64 = cfg!(target_arch = "x86_64");
111        let arch_arm = cfg!(target_arch = "arm");
112        let arch_aarch64 = cfg!(target_arch = "aarch64");
113        let arch_arm_any = arch_arm || arch_aarch64;
114        let arch_riscv32 = cfg!(target_arch = "riscv32");
115        let arch_riscv64 = cfg!(target_arch = "riscv64");
116
117        #[allow(clippy::match_same_arms)]
118        let out = match self {
119            AbiValue::C { unwind } => AbiValue::C { unwind },
120            AbiValue::Rust => AbiValue::Rust,
121            AbiValue::System { unwind } if arch_x86 && os_windows && !has_c_varargs => {
122                AbiValue::Stdcall { unwind }
123            }
124            AbiValue::System { unwind } if arch_arm && os_vexos => AbiValue::Aapcs { unwind },
125            AbiValue::System { unwind } => AbiValue::C { unwind },
126
127            // arm
128            AbiValue::Aapcs { unwind } if arch_arm_any => AbiValue::Aapcs { unwind },
129            AbiValue::Aapcs { .. } => return None,
130
131            // x86
132            AbiValue::Cdecl { unwind } if arch_x86 => AbiValue::C { unwind },
133            AbiValue::Cdecl { unwind } => AbiValue::C { unwind },
134
135            AbiValue::Fastcall { unwind } if arch_x86 => AbiValue::Fastcall { unwind },
136            AbiValue::Fastcall { unwind } if os_windows => AbiValue::C { unwind },
137            AbiValue::Fastcall { .. } => return None,
138
139            AbiValue::Stdcall { unwind } if arch_x86 => AbiValue::Stdcall { unwind },
140            AbiValue::Stdcall { unwind } if os_windows => AbiValue::C { unwind },
141            AbiValue::Stdcall { .. } => return None,
142
143            AbiValue::Thiscall { unwind } if arch_x86 => AbiValue::Thiscall { unwind },
144            AbiValue::Thiscall { .. } => return None,
145
146            AbiValue::Vectorcall { unwind } if arch_x86 || arch_x86_64 => {
147                AbiValue::Vectorcall { unwind }
148            }
149            AbiValue::Vectorcall { .. } => return None,
150
151            AbiValue::SysV64 { unwind } if arch_x86_64 => AbiValue::SysV64 { unwind },
152            AbiValue::Win64 { unwind } if arch_x86_64 => AbiValue::Win64 { unwind },
153            AbiValue::SysV64 { .. } | AbiValue::Win64 { .. } => return None,
154
155            AbiValue::EfiApi if arch_x86_64 => AbiValue::Win64 { unwind: false },
156            AbiValue::EfiApi if arch_arm => AbiValue::Aapcs { unwind: false },
157            AbiValue::EfiApi if arch_x86 || arch_aarch64 || arch_riscv32 || arch_riscv64 => {
158                AbiValue::C { unwind: false }
159            }
160            AbiValue::EfiApi => return None,
161        };
162
163        Some(out)
164    }
165}
166
167impl Display for AbiValue {
168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169        write!(f, "{}", self.to_str())
170    }
171}
172
173macro_rules! abi_kind_impl {
174    (
175        $t:ty => {
176            $(
177                $variant:ident $( { unwind: $uw:literal } )? => $tok:literal
178            ),* $(,)?
179        }
180    ) => {
181        impl $t {
182            /// Returns the string representation of this abi.
183            #[must_use]
184            pub const fn to_str(&self) -> &'static str {
185                match self {
186                    $( Self::$variant $( { unwind: $uw } )? => $tok, )*
187                }
188            }
189
190            /// The same as the [`FromStr`] implementation, but (only!) for use in `const` contexts.
191            #[must_use]
192            pub const fn from_str_const(conv: &'static str) -> Option<Self> {
193                $(
194                    if konst::eq_str(conv, $tok) {
195                        return Some(Self::$variant $( { unwind: $uw } )?);
196                    }
197                )*
198                None
199            }
200        }
201
202        impl FromStr for $t {
203            type Err = ();
204
205            fn from_str(s: &str) -> Result<Self, Self::Err> {
206                match s {
207                    $( $tok => Ok(Self::$variant $( { unwind: $uw } )?), )*
208                    _ => Err(()),
209                }
210            }
211        }
212    };
213}
214
215abi_kind_impl!(AbiValue => {
216    Rust => "Rust",
217    C { unwind: false } => "C",
218    C { unwind: true } => "C-unwind",
219    System { unwind: false } => "system",
220    System { unwind: true } => "system-unwind",
221    Aapcs { unwind: false } => "aapcs",
222    Aapcs { unwind: true } => "aapcs-unwind",
223    Cdecl { unwind: false } => "cdecl",
224    Cdecl { unwind: true } => "cdecl-unwind",
225    Stdcall { unwind: false } => "stdcall",
226    Stdcall { unwind: true } => "stdcall-unwind",
227    Fastcall { unwind: false } => "fastcall",
228    Fastcall { unwind: true } => "fastcall-unwind",
229    Thiscall { unwind: false } => "thiscall",
230    Thiscall { unwind: true } => "thiscall-unwind",
231    Vectorcall { unwind: false } => "vectorcall",
232    Vectorcall { unwind: true } => "vectorcall-unwind",
233    SysV64 { unwind: false } => "sysv64",
234    SysV64 { unwind: true } => "sysv64-unwind",
235    Win64 { unwind: false } => "win64",
236    Win64 { unwind: true } => "win64-unwind",
237    EfiApi => "efiapi"
238});