rust/compiler/rustc_abi/src/extern_abi.rs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

242 lines
7.3 KiB
Rust
Raw Permalink Normal View History

use std::cmp::Ordering;
use std::fmt;
use std::hash::{Hash, Hasher};
#[cfg(feature = "nightly")]
use rustc_data_structures::stable_hasher::{HashStable, StableHasher, StableOrd};
#[cfg(feature = "nightly")]
use rustc_macros::{Decodable, Encodable};
#[cfg(test)]
mod tests;
use ExternAbi as Abi;
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "nightly", derive(Encodable, Decodable))]
pub enum ExternAbi {
// Some of the ABIs come first because every time we add a new ABI, we have to re-bless all the
// hashing tests. These are used in many places, so giving them stable values reduces test
// churn. The specific values are meaningless.
rustc_target: add "unwind" payloads to `Abi` ### Overview This commit begins the implementation work for RFC 2945. For more information, see the rendered RFC [1] and tracking issue [2]. A boolean `unwind` payload is added to the `C`, `System`, `Stdcall`, and `Thiscall` variants, marking whether unwinding across FFI boundaries is acceptable. The cases where each of these variants' `unwind` member is true correspond with the `C-unwind`, `system-unwind`, `stdcall-unwind`, and `thiscall-unwind` ABI strings introduced in RFC 2945 [3]. ### Feature Gate and Unstable Book This commit adds a `c_unwind` feature gate for the new ABI strings. Tests for this feature gate are included in `src/test/ui/c-unwind/`, which ensure that this feature gate works correctly for each of the new ABIs. A new language features entry in the unstable book is added as well. ### Further Work To Be Done This commit does not proceed to implement the new unwinding ABIs, and is intentionally scoped specifically to *defining* the ABIs and their feature flag. ### One Note on Test Churn This will lead to some test churn, in re-blessing hash tests, as the deleted comment in `src/librustc_target/spec/abi.rs` mentioned, because we can no longer guarantee the ordering of the `Abi` variants. While this is a downside, this decision was made bearing in mind that RFC 2945 states the following, in the "Other `unwind` Strings" section [3]: > More unwind variants of existing ABI strings may be introduced, > with the same semantics, without an additional RFC. Adding a new variant for each of these cases, rather than specifying a payload for a given ABI, would quickly become untenable, and make working with the `Abi` enum prone to mistakes. This approach encodes the unwinding information *into* a given ABI, to account for the future possibility of other `-unwind` ABI strings. ### Ignore Directives `ignore-*` directives are used in two of our `*-unwind` ABI test cases. Specifically, the `stdcall-unwind` and `thiscall-unwind` test cases ignore architectures that do not support `stdcall` and `thiscall`, respectively. These directives are cribbed from `src/test/ui/c-variadic/variadic-ffi-1.rs` for `stdcall`, and `src/test/ui/extern/extern-thiscall.rs` for `thiscall`. This would otherwise fail on some targets, see: https://github.com/rust-lang-ci/rust/commit/fcf697f90206e9c87b39d494f94ab35d976bfc60 ### Footnotes [1]: https://github.com/rust-lang/rfcs/blob/master/text/2945-c-unwind-abi.md [2]: https://github.com/rust-lang/rust/issues/74990 [3]: https://github.com/rust-lang/rfcs/blob/master/text/2945-c-unwind-abi.md#other-unwind-abi-strings
2020-08-27 11:49:18 -04:00
Rust,
C {
unwind: bool,
},
Cdecl {
unwind: bool,
},
Stdcall {
unwind: bool,
},
Fastcall {
unwind: bool,
},
Vectorcall {
unwind: bool,
},
Thiscall {
unwind: bool,
},
Aapcs {
unwind: bool,
},
Win64 {
unwind: bool,
},
SysV64 {
unwind: bool,
},
PtxKernel,
Msp430Interrupt,
X86Interrupt,
/// An entry-point function called by the GPU's host
// FIXME: should not be callable from Rust on GPU targets, is for host's use only
GpuKernel,
EfiApi,
2016-05-06 09:32:10 -04:00
AvrInterrupt,
AvrNonBlockingInterrupt,
CCmseNonSecureCall,
2024-08-15 09:55:56 +02:00
CCmseNonSecureEntry,
System {
unwind: bool,
},
RustCall,
/// *Not* a stable ABI, just directly use the Rust types to describe the ABI for LLVM. Even
/// normally ABI-compatible Rust types can become ABI-incompatible with this ABI!
Unadjusted,
/// For things unlikely to be called, where reducing register pressure in
/// `extern "Rust"` callers is worth paying extra cost in the callee.
/// Stronger than just `#[cold]` because `fn` pointers might be incompatible.
RustCold,
feat: `riscv-interrupt-{m,s}` calling conventions Similar to prior support added for the mips430, avr, and x86 targets this change implements the rough equivalent of clang's [`__attribute__((interrupt))`][clang-attr] for riscv targets, enabling e.g. ```rust static mut CNT: usize = 0; pub extern "riscv-interrupt-m" fn isr_m() { unsafe { CNT += 1; } } ``` to produce highly effective assembly like: ```asm pub extern "riscv-interrupt-m" fn isr_m() { 420003a0: 1141 addi sp,sp,-16 unsafe { CNT += 1; 420003a2: c62a sw a0,12(sp) 420003a4: c42e sw a1,8(sp) 420003a6: 3fc80537 lui a0,0x3fc80 420003aa: 63c52583 lw a1,1596(a0) # 3fc8063c <_ZN12esp_riscv_rt3CNT17hcec3e3a214887d53E.0> 420003ae: 0585 addi a1,a1,1 420003b0: 62b52e23 sw a1,1596(a0) } } 420003b4: 4532 lw a0,12(sp) 420003b6: 45a2 lw a1,8(sp) 420003b8: 0141 addi sp,sp,16 420003ba: 30200073 mret ``` (disassembly via `riscv64-unknown-elf-objdump -C -S --disassemble ./esp32c3-hal/target/riscv32imc-unknown-none-elf/release/examples/gpio_interrupt`) This outcome is superior to hand-coded interrupt routines which, lacking visibility into any non-assembly body of the interrupt handler, have to be very conservative and save the [entire CPU state to the stack frame][full-frame-save]. By instead asking LLVM to only save the registers that it uses, we defer the decision to the tool with the best context: it can more accurately account for the cost of spills if it knows that every additional register used is already at the cost of an implicit spill. At the LLVM level, this is apparently [implemented by] marking every register as "[callee-save]," matching the semantics of an interrupt handler nicely (it has to leave the CPU state just as it found it after its `{m|s}ret`). This approach is not suitable for every interrupt handler, as it makes no attempt to e.g. save the state in a user-accessible stack frame. For a full discussion of those challenges and tradeoffs, please refer to [the interrupt calling conventions RFC][rfc]. Inside rustc, this implementation differs from prior art because LLVM does not expose the "all-saved" function flavor as a calling convention directly, instead preferring to use an attribute that allows for differentiating between "machine-mode" and "superivsor-mode" interrupts. Finally, some effort has been made to guide those who may not yet be aware of the differences between machine-mode and supervisor-mode interrupts as to why no `riscv-interrupt` calling convention is exposed through rustc, and similarly for why `riscv-interrupt-u` makes no appearance (as it would complicate future LLVM upgrades). [clang-attr]: https://clang.llvm.org/docs/AttributeReference.html#interrupt-risc-v [full-frame-save]: https://github.com/esp-rs/esp-riscv-rt/blob/9281af2ecffe13e40992917316f36920c26acaf3/src/lib.rs#L440-L469 [implemented by]: https://github.com/llvm/llvm-project/blob/b7fb2a3fec7c187d58a6d338ab512d9173bca987/llvm/lib/Target/RISCV/RISCVRegisterInfo.cpp#L61-L67 [callee-save]: https://github.com/llvm/llvm-project/blob/973f1fe7a8591c7af148e573491ab68cc15b6ecf/llvm/lib/Target/RISCV/RISCVCallingConv.td#L30-L37 [rfc]: https://github.com/rust-lang/rfcs/pull/3246
2023-05-23 15:08:23 -07:00
RiscvInterruptM,
RiscvInterruptS,
}
macro_rules! abi_impls {
($e_name:ident = {
$($variant:ident $({ unwind: $uw:literal })? =><= $tok:literal,)*
}) => {
impl $e_name {
pub const ALL_VARIANTS: &[Self] = &[
$($e_name::$variant $({ unwind: $uw })*,)*
];
pub const fn as_str(&self) -> &'static str {
match self {
$($e_name::$variant $( { unwind: $uw } )* => $tok,)*
}
}
}
impl ::core::str::FromStr for $e_name {
type Err = AbiFromStrErr;
fn from_str(s: &str) -> Result<$e_name, Self::Err> {
match s {
$($tok => Ok($e_name::$variant $({ unwind: $uw })*),)*
_ => Err(AbiFromStrErr::Unknown),
}
}
}
}
}
#[derive(Debug)]
pub enum AbiFromStrErr {
Unknown,
}
abi_impls! {
ExternAbi = {
C { unwind: false } =><= "C",
CCmseNonSecureCall =><= "C-cmse-nonsecure-call",
CCmseNonSecureEntry =><= "C-cmse-nonsecure-entry",
C { unwind: true } =><= "C-unwind",
Rust =><= "Rust",
Aapcs { unwind: false } =><= "aapcs",
Aapcs { unwind: true } =><= "aapcs-unwind",
AvrInterrupt =><= "avr-interrupt",
AvrNonBlockingInterrupt =><= "avr-non-blocking-interrupt",
Cdecl { unwind: false } =><= "cdecl",
Cdecl { unwind: true } =><= "cdecl-unwind",
EfiApi =><= "efiapi",
Fastcall { unwind: false } =><= "fastcall",
Fastcall { unwind: true } =><= "fastcall-unwind",
GpuKernel =><= "gpu-kernel",
Msp430Interrupt =><= "msp430-interrupt",
PtxKernel =><= "ptx-kernel",
RiscvInterruptM =><= "riscv-interrupt-m",
RiscvInterruptS =><= "riscv-interrupt-s",
RustCall =><= "rust-call",
RustCold =><= "rust-cold",
Stdcall { unwind: false } =><= "stdcall",
Stdcall { unwind: true } =><= "stdcall-unwind",
System { unwind: false } =><= "system",
System { unwind: true } =><= "system-unwind",
SysV64 { unwind: false } =><= "sysv64",
SysV64 { unwind: true } =><= "sysv64-unwind",
Thiscall { unwind: false } =><= "thiscall",
Thiscall { unwind: true } =><= "thiscall-unwind",
Unadjusted =><= "unadjusted",
Vectorcall { unwind: false } =><= "vectorcall",
Vectorcall { unwind: true } =><= "vectorcall-unwind",
Win64 { unwind: false } =><= "win64",
Win64 { unwind: true } =><= "win64-unwind",
X86Interrupt =><= "x86-interrupt",
}
}
impl Ord for ExternAbi {
fn cmp(&self, rhs: &Self) -> Ordering {
self.as_str().cmp(rhs.as_str())
}
}
impl PartialOrd for ExternAbi {
fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
Some(self.cmp(rhs))
}
}
impl PartialEq for ExternAbi {
fn eq(&self, rhs: &Self) -> bool {
self.cmp(rhs) == Ordering::Equal
}
}
impl Eq for ExternAbi {}
impl Hash for ExternAbi {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
// double-assurance of a prefix breaker
u32::from_be_bytes(*b"ABI\0").hash(state);
}
}
#[cfg(feature = "nightly")]
impl<C> HashStable<C> for ExternAbi {
#[inline]
fn hash_stable(&self, _: &mut C, hasher: &mut StableHasher) {
Hash::hash(self, hasher);
}
}
#[cfg(feature = "nightly")]
impl StableOrd for ExternAbi {
const CAN_USE_UNSTABLE_SORT: bool = true;
// because each ABI is hashed like a string, there is no possible instability
const THIS_IMPLEMENTATION_HAS_BEEN_TRIPLE_CHECKED: () = ();
}
impl ExternAbi {
/// An ABI "like Rust"
///
/// These ABIs are fully controlled by the Rust compiler, which means they
/// - support unwinding with `-Cpanic=unwind`, unlike `extern "C"`
/// - often diverge from the C ABI
/// - are subject to change between compiler versions
pub fn is_rustic_abi(self) -> bool {
use ExternAbi::*;
matches!(self, Rust | RustCall | RustCold)
}
pub fn supports_varargs(self) -> bool {
// * C and Cdecl obviously support varargs.
// * C can be based on Aapcs, SysV64 or Win64, so they must support varargs.
// * EfiApi is based on Win64 or C, so it also supports it.
//
// * Stdcall does not, because it would be impossible for the callee to clean
// up the arguments. (callee doesn't know how many arguments are there)
// * Same for Fastcall, Vectorcall and Thiscall.
// * Other calling conventions are related to hardware or the compiler itself.
match self {
Self::C { .. }
| Self::Cdecl { .. }
| Self::Aapcs { .. }
| Self::Win64 { .. }
| Self::SysV64 { .. }
| Self::EfiApi => true,
_ => false,
}
}
}
pub fn all_names() -> Vec<&'static str> {
ExternAbi::ALL_VARIANTS.iter().map(|abi| abi.as_str()).collect()
}
impl ExternAbi {
2021-07-08 21:58:05 +02:00
/// Default ABI chosen for `extern fn` declarations without an explicit ABI.
pub const FALLBACK: Abi = Abi::C { unwind: false };
2018-08-09 15:42:43 +02:00
pub fn name(self) -> &'static str {
self.as_str()
}
}
impl fmt::Display for ExternAbi {
2019-02-08 21:00:07 +09:00
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "\"{}\"", self.as_str())
}
}