error when ABI does not support guaranteed tail calls

This commit is contained in:
Folkert de Vries 2025-11-12 20:04:38 +01:00
parent 9312cd6d38
commit 78beefed84
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
6 changed files with 163 additions and 0 deletions

View file

@ -276,6 +276,51 @@ impl ExternAbi {
_ => CVariadicStatus::NotSupported,
}
}
/// Returns whether the ABI supports guaranteed tail calls.
#[cfg(feature = "nightly")]
pub fn supports_guaranteed_tail_call(self) -> bool {
match self {
Self::CmseNonSecureCall | Self::CmseNonSecureEntry => {
// See https://godbolt.org/z/9jhdeqErv. The CMSE calling conventions clear registers
// before returning, and hence cannot guarantee a tail call.
false
}
Self::AvrInterrupt
| Self::AvrNonBlockingInterrupt
| Self::Msp430Interrupt
| Self::RiscvInterruptM
| Self::RiscvInterruptS
| Self::X86Interrupt => {
// See https://godbolt.org/z/Edfjnxxcq. Interrupts cannot be called directly.
false
}
Self::GpuKernel | Self::PtxKernel => {
// See https://godbolt.org/z/jq5TE5jK1.
false
}
Self::Custom => {
// This ABI does not support calls at all (except via assembly).
false
}
Self::C { .. }
| Self::System { .. }
| Self::Rust
| Self::RustCall
| Self::RustCold
| Self::RustInvalid
| Self::Unadjusted
| Self::EfiApi
| Self::Aapcs { .. }
| Self::Cdecl { .. }
| Self::Stdcall { .. }
| Self::Fastcall { .. }
| Self::Thiscall { .. }
| Self::Vectorcall { .. }
| Self::SysV64 { .. }
| Self::Win64 { .. } => true,
}
}
}
pub fn all_names() -> Vec<&'static str> {

View file

@ -135,6 +135,10 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
self.report_abi_mismatch(expr.span, caller_sig.abi, callee_sig.abi);
}
if !callee_sig.abi.supports_guaranteed_tail_call() {
self.report_unsupported_abi(expr.span, callee_sig.abi);
}
// FIXME(explicit_tail_calls): this currently fails for cases where opaques are used.
// e.g.
// ```
@ -358,6 +362,16 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
self.found_errors = Err(err);
}
fn report_unsupported_abi(&mut self, sp: Span, callee_abi: ExternAbi) {
let err = self
.tcx
.dcx()
.struct_span_err(sp, "ABI does not support guaranteed tail calls")
.with_note(format!("`become` is not supported for `extern {callee_abi}` functions"))
.emit();
self.found_errors = Err(err);
}
fn report_signature_mismatch(
&mut self,
sp: Span,

View file

@ -0,0 +1,38 @@
//@ add-minicore
//@ ignore-backends: gcc
//@ compile-flags: --target thumbv8m.main-none-eabi --crate-type lib
//@ needs-llvm-components: arm
#![expect(incomplete_features)]
#![feature(no_core, explicit_tail_calls, abi_cmse_nonsecure_call)]
#![no_core]
extern crate minicore;
use minicore::*;
unsafe extern "C" {
safe fn magic() -> extern "cmse-nonsecure-call" fn(u32, u32) -> u32;
}
// The `cmse-nonsecure-call` ABI can only occur on function pointers:
//
// - a `cmse-nonsecure-call` definition throws an error
// - a `cmse-nonsecure-call` become in a definition with any other ABI is an ABI mismatch
#[no_mangle]
extern "cmse-nonsecure-call" fn become_nonsecure_call_1(x: u32, y: u32) -> u32 {
//~^ ERROR the `"cmse-nonsecure-call"` ABI is only allowed on function pointers
unsafe {
let f = magic();
become f(1, 2)
//~^ ERROR ABI does not support guaranteed tail calls
}
}
#[no_mangle]
extern "C" fn become_nonsecure_call_2(x: u32, y: u32) -> u32 {
unsafe {
let f = magic();
become f(1, 2)
//~^ ERROR mismatched function ABIs
//~| ERROR ABI does not support guaranteed tail calls
}
}

View file

@ -0,0 +1,34 @@
error[E0781]: the `"cmse-nonsecure-call"` ABI is only allowed on function pointers
--> $DIR/cmse-nonsecure-call.rs:21:1
|
LL | extern "cmse-nonsecure-call" fn become_nonsecure_call_1(x: u32, y: u32) -> u32 {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: ABI does not support guaranteed tail calls
--> $DIR/cmse-nonsecure-call.rs:25:9
|
LL | become f(1, 2)
| ^^^^^^^^^^^^^^
|
= note: `become` is not supported for `extern "cmse-nonsecure-call"` functions
error: mismatched function ABIs
--> $DIR/cmse-nonsecure-call.rs:34:9
|
LL | become f(1, 2)
| ^^^^^^^^^^^^^^
|
= note: `become` requires caller and callee to have the same ABI
= note: caller ABI is `"C"`, while callee ABI is `"cmse-nonsecure-call"`
error: ABI does not support guaranteed tail calls
--> $DIR/cmse-nonsecure-call.rs:34:9
|
LL | become f(1, 2)
| ^^^^^^^^^^^^^^
|
= note: `become` is not supported for `extern "cmse-nonsecure-call"` functions
error: aborting due to 4 previous errors
For more information about this error, try `rustc --explain E0781`.

View file

@ -0,0 +1,22 @@
//@ add-minicore
//@ ignore-backends: gcc
//@ compile-flags: --target thumbv8m.main-none-eabi --crate-type lib
//@ needs-llvm-components: arm
#![expect(incomplete_features)]
#![feature(no_core, explicit_tail_calls, cmse_nonsecure_entry)]
#![no_core]
extern crate minicore;
use minicore::*;
#[inline(never)]
extern "cmse-nonsecure-entry" fn entry(c: bool, x: u32, y: u32) -> u32 {
if c { x } else { y }
}
// A `cmse-nonsecure-entry` clears registers before returning, so a tail call cannot be guaranteed.
#[unsafe(no_mangle)]
extern "cmse-nonsecure-entry" fn become_nonsecure_entry(c: bool, x: u32, y: u32) -> u32 {
become entry(c, x, y)
//~^ ERROR ABI does not support guaranteed tail calls
}

View file

@ -0,0 +1,10 @@
error: ABI does not support guaranteed tail calls
--> $DIR/cmse-nonsecure-entry.rs:20:5
|
LL | become entry(c, x, y)
| ^^^^^^^^^^^^^^^^^^^^^
|
= note: `become` is not supported for `extern "cmse-nonsecure-entry"` functions
error: aborting due to 1 previous error