From 78beefed8483cc7b33cf678b6408927dcf5ffb8b Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Wed, 12 Nov 2025 20:04:38 +0100 Subject: [PATCH] error when ABI does not support guaranteed tail calls --- compiler/rustc_abi/src/extern_abi.rs | 45 +++++++++++++++++++ .../rustc_mir_build/src/check_tail_calls.rs | 14 ++++++ .../unsupported-abi/cmse-nonsecure-call.rs | 38 ++++++++++++++++ .../cmse-nonsecure-call.stderr | 34 ++++++++++++++ .../unsupported-abi/cmse-nonsecure-entry.rs | 22 +++++++++ .../cmse-nonsecure-entry.stderr | 10 +++++ 6 files changed, 163 insertions(+) create mode 100644 tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-call.rs create mode 100644 tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-call.stderr create mode 100644 tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-entry.rs create mode 100644 tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-entry.stderr diff --git a/compiler/rustc_abi/src/extern_abi.rs b/compiler/rustc_abi/src/extern_abi.rs index e3b2b1eff72d4..0625759359ea3 100644 --- a/compiler/rustc_abi/src/extern_abi.rs +++ b/compiler/rustc_abi/src/extern_abi.rs @@ -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> { diff --git a/compiler/rustc_mir_build/src/check_tail_calls.rs b/compiler/rustc_mir_build/src/check_tail_calls.rs index 9115c17f37525..b8547e288027a 100644 --- a/compiler/rustc_mir_build/src/check_tail_calls.rs +++ b/compiler/rustc_mir_build/src/check_tail_calls.rs @@ -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, diff --git a/tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-call.rs b/tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-call.rs new file mode 100644 index 0000000000000..028716a14c7b4 --- /dev/null +++ b/tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-call.rs @@ -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 + } +} diff --git a/tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-call.stderr b/tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-call.stderr new file mode 100644 index 0000000000000..08f11a4bd4b71 --- /dev/null +++ b/tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-call.stderr @@ -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`. diff --git a/tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-entry.rs b/tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-entry.rs new file mode 100644 index 0000000000000..006fa538c4891 --- /dev/null +++ b/tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-entry.rs @@ -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 +} diff --git a/tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-entry.stderr b/tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-entry.stderr new file mode 100644 index 0000000000000..3acbe8c5bfaa5 --- /dev/null +++ b/tests/ui/explicit-tail-calls/unsupported-abi/cmse-nonsecure-entry.stderr @@ -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 +