Skip to content

Commit c7d8623

Browse files
authored
Rollup merge of rust-lang#148878 - folkertdev:tail-call-unsupported-abi, r=WaffleLapkin
error when ABI does not support guaranteed tail calls Some ABIs cannot support guaranteed tail calls. There isn't really an exhaustive list, so this is a best effort. Conveniently, we already disallow calling most of these directly anyway. The only exception that I was able to trigger an LLVM assertion with so far was `cmse-nonsecure-entry`. For that calling convention, LLVM specifically notes that (guaranteed) tail calls cannot be supported: https://github.com/llvm/llvm-project/blob/28dbbba6c3a4e026e085c48cc022cb97b5d8bc6d/llvm/lib/Target/ARM/ARMISelLowering.cpp#L2331-L2335 --- I have some doubts about the implementation here though. I think it would be nicer to use `CanonAbi`, and move the `become` ABI check into `rustc_hir_typeck`, similar to `check_call_abi`: https://github.com/rust-lang/rust/blob/d6deffe2debecc66501e50f9573214139ab4d678/compiler/rustc_hir_typeck/src/callee.rs#L157-L194 Both the check for whether an ABI is callable and whether it supports guaranteed tail calls can then be methods (containing exhaustive matches) on `CanonAbi`. I'm however not sure - if the ABI checks are deliberately only performed when constructing MIR - what assumptions can be made about the `call` expression in [`check_expr_become`](https://github.com/rust-lang/rust/blob/d6deffe2debecc66501e50f9573214139ab4d678/compiler/rustc_hir_typeck/src/expr.rs#L1126-L1150), it looks like currently the check that the "argument" to `become` is a function call also only occurs later during MIR construction Are there issues with validating the ABI earlier in `rustc_hir_typeck` that I'm overlooking? I believe that we should already know the call's ABI and whether it is c-variadic at that point. cc ```@workingjubilee``` for `CanonAbi`, ```@davidtwco``` for cmse r? ```@WaffleLapkin```
2 parents 4dda4c9 + 78beefe commit c7d8623

File tree

6 files changed

+163
-0
lines changed

6 files changed

+163
-0
lines changed

compiler/rustc_abi/src/extern_abi.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,51 @@ impl ExternAbi {
276276
_ => CVariadicStatus::NotSupported,
277277
}
278278
}
279+
280+
/// Returns whether the ABI supports guaranteed tail calls.
281+
#[cfg(feature = "nightly")]
282+
pub fn supports_guaranteed_tail_call(self) -> bool {
283+
match self {
284+
Self::CmseNonSecureCall | Self::CmseNonSecureEntry => {
285+
// See https://godbolt.org/z/9jhdeqErv. The CMSE calling conventions clear registers
286+
// before returning, and hence cannot guarantee a tail call.
287+
false
288+
}
289+
Self::AvrInterrupt
290+
| Self::AvrNonBlockingInterrupt
291+
| Self::Msp430Interrupt
292+
| Self::RiscvInterruptM
293+
| Self::RiscvInterruptS
294+
| Self::X86Interrupt => {
295+
// See https://godbolt.org/z/Edfjnxxcq. Interrupts cannot be called directly.
296+
false
297+
}
298+
Self::GpuKernel | Self::PtxKernel => {
299+
// See https://godbolt.org/z/jq5TE5jK1.
300+
false
301+
}
302+
Self::Custom => {
303+
// This ABI does not support calls at all (except via assembly).
304+
false
305+
}
306+
Self::C { .. }
307+
| Self::System { .. }
308+
| Self::Rust
309+
| Self::RustCall
310+
| Self::RustCold
311+
| Self::RustInvalid
312+
| Self::Unadjusted
313+
| Self::EfiApi
314+
| Self::Aapcs { .. }
315+
| Self::Cdecl { .. }
316+
| Self::Stdcall { .. }
317+
| Self::Fastcall { .. }
318+
| Self::Thiscall { .. }
319+
| Self::Vectorcall { .. }
320+
| Self::SysV64 { .. }
321+
| Self::Win64 { .. } => true,
322+
}
323+
}
279324
}
280325

281326
pub fn all_names() -> Vec<&'static str> {

compiler/rustc_mir_build/src/check_tail_calls.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
135135
self.report_abi_mismatch(expr.span, caller_sig.abi, callee_sig.abi);
136136
}
137137

138+
if !callee_sig.abi.supports_guaranteed_tail_call() {
139+
self.report_unsupported_abi(expr.span, callee_sig.abi);
140+
}
141+
138142
// FIXME(explicit_tail_calls): this currently fails for cases where opaques are used.
139143
// e.g.
140144
// ```
@@ -358,6 +362,16 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
358362
self.found_errors = Err(err);
359363
}
360364

365+
fn report_unsupported_abi(&mut self, sp: Span, callee_abi: ExternAbi) {
366+
let err = self
367+
.tcx
368+
.dcx()
369+
.struct_span_err(sp, "ABI does not support guaranteed tail calls")
370+
.with_note(format!("`become` is not supported for `extern {callee_abi}` functions"))
371+
.emit();
372+
self.found_errors = Err(err);
373+
}
374+
361375
fn report_signature_mismatch(
362376
&mut self,
363377
sp: Span,
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//@ add-minicore
2+
//@ ignore-backends: gcc
3+
//@ compile-flags: --target thumbv8m.main-none-eabi --crate-type lib
4+
//@ needs-llvm-components: arm
5+
#![expect(incomplete_features)]
6+
#![feature(no_core, explicit_tail_calls, abi_cmse_nonsecure_call)]
7+
#![no_core]
8+
9+
extern crate minicore;
10+
use minicore::*;
11+
12+
unsafe extern "C" {
13+
safe fn magic() -> extern "cmse-nonsecure-call" fn(u32, u32) -> u32;
14+
}
15+
16+
// The `cmse-nonsecure-call` ABI can only occur on function pointers:
17+
//
18+
// - a `cmse-nonsecure-call` definition throws an error
19+
// - a `cmse-nonsecure-call` become in a definition with any other ABI is an ABI mismatch
20+
#[no_mangle]
21+
extern "cmse-nonsecure-call" fn become_nonsecure_call_1(x: u32, y: u32) -> u32 {
22+
//~^ ERROR the `"cmse-nonsecure-call"` ABI is only allowed on function pointers
23+
unsafe {
24+
let f = magic();
25+
become f(1, 2)
26+
//~^ ERROR ABI does not support guaranteed tail calls
27+
}
28+
}
29+
30+
#[no_mangle]
31+
extern "C" fn become_nonsecure_call_2(x: u32, y: u32) -> u32 {
32+
unsafe {
33+
let f = magic();
34+
become f(1, 2)
35+
//~^ ERROR mismatched function ABIs
36+
//~| ERROR ABI does not support guaranteed tail calls
37+
}
38+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
error[E0781]: the `"cmse-nonsecure-call"` ABI is only allowed on function pointers
2+
--> $DIR/cmse-nonsecure-call.rs:21:1
3+
|
4+
LL | extern "cmse-nonsecure-call" fn become_nonsecure_call_1(x: u32, y: u32) -> u32 {
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
7+
error: ABI does not support guaranteed tail calls
8+
--> $DIR/cmse-nonsecure-call.rs:25:9
9+
|
10+
LL | become f(1, 2)
11+
| ^^^^^^^^^^^^^^
12+
|
13+
= note: `become` is not supported for `extern "cmse-nonsecure-call"` functions
14+
15+
error: mismatched function ABIs
16+
--> $DIR/cmse-nonsecure-call.rs:34:9
17+
|
18+
LL | become f(1, 2)
19+
| ^^^^^^^^^^^^^^
20+
|
21+
= note: `become` requires caller and callee to have the same ABI
22+
= note: caller ABI is `"C"`, while callee ABI is `"cmse-nonsecure-call"`
23+
24+
error: ABI does not support guaranteed tail calls
25+
--> $DIR/cmse-nonsecure-call.rs:34:9
26+
|
27+
LL | become f(1, 2)
28+
| ^^^^^^^^^^^^^^
29+
|
30+
= note: `become` is not supported for `extern "cmse-nonsecure-call"` functions
31+
32+
error: aborting due to 4 previous errors
33+
34+
For more information about this error, try `rustc --explain E0781`.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//@ add-minicore
2+
//@ ignore-backends: gcc
3+
//@ compile-flags: --target thumbv8m.main-none-eabi --crate-type lib
4+
//@ needs-llvm-components: arm
5+
#![expect(incomplete_features)]
6+
#![feature(no_core, explicit_tail_calls, cmse_nonsecure_entry)]
7+
#![no_core]
8+
9+
extern crate minicore;
10+
use minicore::*;
11+
12+
#[inline(never)]
13+
extern "cmse-nonsecure-entry" fn entry(c: bool, x: u32, y: u32) -> u32 {
14+
if c { x } else { y }
15+
}
16+
17+
// A `cmse-nonsecure-entry` clears registers before returning, so a tail call cannot be guaranteed.
18+
#[unsafe(no_mangle)]
19+
extern "cmse-nonsecure-entry" fn become_nonsecure_entry(c: bool, x: u32, y: u32) -> u32 {
20+
become entry(c, x, y)
21+
//~^ ERROR ABI does not support guaranteed tail calls
22+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: ABI does not support guaranteed tail calls
2+
--> $DIR/cmse-nonsecure-entry.rs:20:5
3+
|
4+
LL | become entry(c, x, y)
5+
| ^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: `become` is not supported for `extern "cmse-nonsecure-entry"` functions
8+
9+
error: aborting due to 1 previous error
10+

0 commit comments

Comments
 (0)