Skip to content

Commit dbdbf68

Browse files
Rollup merge of rust-lang#157983 - folkertdev:tailcc-lift-same-signature-restriction, r=WaffleLapkin
Lift the same-signature restriction for `extern "tail"` tracking issue: rust-lang#157427 The `extern "tail"` calling convention uses callee cleanup (i.e. the callee restores the stack, not the caller). Hence, the same-signature restriction that is normally required to codegen tail calls does not apply. We need the ABI to deviate from `extern "Rust"` to make sure indirect arguments are passed by stack offset, not via a pointer into the caller's stack frame (the value would potentially be overwritten). For standard tail calls we work around this problem by storing the value in the caller's caller, but for `extern "tail"` that doesn't work reliably because the signatures can be different. I'm not sure about unsized arguments. That feature seems really broken, so I'm not sure how much work we should put into trying to do something reasonable there. Fundamentally I don't think we can support unsized arguments in `extern "tail"` calls. Also we can't really promote using this yet due to `tailcc` being a bit broken on LLVM 22 on x86_64. r? WaffleLapkin cc @bjorn3
2 parents cddcbec + d8d4b89 commit dbdbf68

12 files changed

Lines changed: 240 additions & 13 deletions

File tree

compiler/rustc_codegen_ssa/src/mir/block.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1317,7 +1317,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
13171317
}
13181318
LocalRef::Operand(arg) => {
13191319
let Ref(place_value) = arg.val else {
1320-
bug!("only `Ref` should use `PassMode::Indirect`");
1320+
bug!(
1321+
"only `Ref` should use `PassMode::Indirect`, but got {:?}",
1322+
arg.val
1323+
);
13211324
};
13221325
bx.typed_place_copy(place_value, tmp.val, fn_abi.args[i].layout);
13231326
op.val = arg.val;

compiler/rustc_hir_typeck/src/check.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ pub(super) fn check_fn<'a, 'tcx>(
9090
}
9191

9292
// Check that argument is Sized.
93-
if !params_can_be_unsized {
93+
if !params_can_be_unsized || fn_sig.abi() == rustc_abi::ExternAbi::RustTail {
9494
fcx.require_type_is_sized(
9595
param_ty,
9696
param.ty_span,

compiler/rustc_mir_build/src/check_tail_calls.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,9 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
147147
// ```
148148
// we should think what is the expected behavior here.
149149
// (we should probably just accept this by revealing opaques?)
150-
if caller_sig.inputs_and_output != callee_sig.inputs_and_output {
150+
if caller_sig.inputs_and_output != callee_sig.inputs_and_output
151+
&& !matches!(callee_sig.abi(), ExternAbi::RustTail)
152+
{
151153
let caller_ty = self.tcx.type_of(self.caller_def_id).skip_binder();
152154

153155
self.report_signature_mismatch(
@@ -189,6 +191,12 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
189191
if callee_sig.c_variadic() {
190192
self.report_c_variadic_callee(expr.span);
191193
}
194+
195+
for &arg_ty in callee_sig.inputs() {
196+
if !arg_ty.is_sized(self.tcx, self.typing_env) {
197+
self.report_unsized_argument(expr.span, arg_ty);
198+
}
199+
}
192200
}
193201

194202
/// Returns true if the caller function needs a location argument
@@ -417,6 +425,17 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
417425

418426
self.found_errors = Err(err);
419427
}
428+
429+
fn report_unsized_argument(&mut self, sp: Span, arg_ty: Ty<'tcx>) {
430+
let err = self
431+
.tcx
432+
.dcx()
433+
.struct_span_err(sp, format!("unsized arguments cannot be used in a tail call"))
434+
.with_note(format!("unsized argument of type `{arg_ty}`"))
435+
.emit();
436+
437+
self.found_errors = Err(err);
438+
}
420439
}
421440

422441
impl<'a, 'tcx> Visitor<'a, 'tcx> for TailCallCkVisitor<'a, 'tcx> {

compiler/rustc_target/src/callconv/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,9 @@ impl<'a, Ty> FnAbi<'a, Ty> {
827827
ArgAttribute::default()
828828
};
829829
arg.cast_to_with_attrs(Reg { kind: RegKind::Integer, size }, attr.into());
830+
} else if self.conv == CanonAbi::RustTail {
831+
assert!(arg.layout.is_sized(), "extern \"tail\" arguments must be sized");
832+
arg.pass_by_stack_offset(None);
830833
}
831834
}
832835

compiler/rustc_ty_utils/src/abi.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,8 +458,10 @@ fn fn_abi_sanity_check<'tcx>(
458458
// omitted entirely in the calling convention.
459459
assert!(arg.is_ignore());
460460
}
461-
if let PassMode::Indirect { on_stack, .. } = arg.mode {
462-
assert!(!on_stack, "rust abi shouldn't use on_stack");
461+
if let PassMode::Indirect { on_stack, .. } = arg.mode
462+
&& spec_abi != ExternAbi::RustTail
463+
{
464+
assert!(!on_stack, "rustic abi {spec_abi:?} shouldn't use on_stack");
463465
}
464466
} else if arg.layout.pass_indirectly_in_non_rustic_abis(cx) {
465467
assert_matches!(
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
error[E0277]: the size for values of type `[u8]` cannot be known at compilation time
2+
--> $DIR/no-unsized-arguments.rs:40:42
3+
|
4+
LL | extern "tail" fn unsized_argument(x: [u8]) -> u8 {
5+
| ^^^^ doesn't have a size known at compile-time
6+
|
7+
= help: the trait `Sized` is not implemented for `[u8]`
8+
help: function arguments must have a statically known size, borrowed slices always have a known size
9+
|
10+
LL | extern "tail" fn unsized_argument(x: &[u8]) -> u8 {
11+
| +
12+
13+
error: unsized arguments cannot be used in a tail call
14+
--> $DIR/no-unsized-arguments.rs:33:5
15+
|
16+
LL | become unsized_argument(b);
17+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
18+
|
19+
= note: unsized argument of type `[u8]`
20+
21+
error: unsized arguments cannot be used in a tail call
22+
--> $DIR/no-unsized-arguments.rs:48:5
23+
|
24+
LL | become unsized_argument(*b);
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
26+
|
27+
= note: unsized argument of type `[u8]`
28+
29+
error: aborting due to 3 previous errors
30+
31+
For more information about this error, try `rustc --explain E0277`.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//@ add-minicore
2+
//@ ignore-backends: gcc
3+
//@ min-llvm-version: 22
4+
//
5+
//@ revisions: x86 x86_64 aarch64
6+
//
7+
//@ [x86] compile-flags: --target=i686-unknown-linux-gnu
8+
//@ [x86] needs-llvm-components: x86
9+
//@ [x86_64] compile-flags: --target=x86_64-unknown-linux-gnu
10+
//@ [x86_64] needs-llvm-components: x86
11+
//@ [aarch64] compile-flags: --target=aarch64-unknown-linux-gnu
12+
//@ [aarch64] needs-llvm-components: aarch64
13+
#![feature(explicit_tail_calls, rust_tail_cc, unsized_fn_params, no_core)]
14+
#![allow(incomplete_features, internal_features)]
15+
#![no_core]
16+
#![crate_type = "lib"]
17+
18+
extern crate minicore;
19+
use minicore::*;
20+
21+
extern "C" {
22+
fn extract(_: [u8]) -> u8;
23+
}
24+
25+
fn vanilla(b: [u8]) -> u8 {
26+
fn unsized_argument(x: [u8]) -> u8 {
27+
unsafe { extract(x) }
28+
}
29+
30+
// Non-tail call.
31+
let _ = unsized_argument(b);
32+
33+
become unsized_argument(b);
34+
//~^ ERROR unsized arguments cannot be used in a tail call
35+
}
36+
37+
extern "tail" fn tailcc(b: &[u8]) -> u8 {
38+
// `extern "tail"` is special because we also can't unsized parameters in standard definitions
39+
// and calls.
40+
extern "tail" fn unsized_argument(x: [u8]) -> u8 {
41+
//~^ ERROR the size for values of type `[u8]` cannot be known at compilation time
42+
unsafe { extract(x) }
43+
}
44+
45+
// Vanilla call.
46+
let _ = unsized_argument(*b);
47+
48+
become unsized_argument(*b);
49+
//~^ ERROR unsized arguments cannot be used in a tail call
50+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
error[E0277]: the size for values of type `[u8]` cannot be known at compilation time
2+
--> $DIR/no-unsized-arguments.rs:40:42
3+
|
4+
LL | extern "tail" fn unsized_argument(x: [u8]) -> u8 {
5+
| ^^^^ doesn't have a size known at compile-time
6+
|
7+
= help: the trait `Sized` is not implemented for `[u8]`
8+
help: function arguments must have a statically known size, borrowed slices always have a known size
9+
|
10+
LL | extern "tail" fn unsized_argument(x: &[u8]) -> u8 {
11+
| +
12+
13+
error: unsized arguments cannot be used in a tail call
14+
--> $DIR/no-unsized-arguments.rs:33:5
15+
|
16+
LL | become unsized_argument(b);
17+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
18+
|
19+
= note: unsized argument of type `[u8]`
20+
21+
error: unsized arguments cannot be used in a tail call
22+
--> $DIR/no-unsized-arguments.rs:48:5
23+
|
24+
LL | become unsized_argument(*b);
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
26+
|
27+
= note: unsized argument of type `[u8]`
28+
29+
error: aborting due to 3 previous errors
30+
31+
For more information about this error, try `rustc --explain E0277`.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
error[E0277]: the size for values of type `[u8]` cannot be known at compilation time
2+
--> $DIR/no-unsized-arguments.rs:40:42
3+
|
4+
LL | extern "tail" fn unsized_argument(x: [u8]) -> u8 {
5+
| ^^^^ doesn't have a size known at compile-time
6+
|
7+
= help: the trait `Sized` is not implemented for `[u8]`
8+
help: function arguments must have a statically known size, borrowed slices always have a known size
9+
|
10+
LL | extern "tail" fn unsized_argument(x: &[u8]) -> u8 {
11+
| +
12+
13+
error: unsized arguments cannot be used in a tail call
14+
--> $DIR/no-unsized-arguments.rs:33:5
15+
|
16+
LL | become unsized_argument(b);
17+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
18+
|
19+
= note: unsized argument of type `[u8]`
20+
21+
error: unsized arguments cannot be used in a tail call
22+
--> $DIR/no-unsized-arguments.rs:48:5
23+
|
24+
LL | become unsized_argument(*b);
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
26+
|
27+
= note: unsized argument of type `[u8]`
28+
29+
error: aborting due to 3 previous errors
30+
31+
For more information about this error, try `rustc --explain E0277`.
Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#![expect(incomplete_features)]
2-
#![feature(explicit_tail_calls)]
2+
#![feature(explicit_tail_calls, rust_tail_cc)]
33
#![feature(c_variadic)]
44

55
fn _f0((): ()) {
@@ -8,26 +8,30 @@ fn _f0((): ()) {
88

99
fn _g0() {}
1010

11-
1211
fn _f1() {
1312
become _g1(()); //~ error: mismatched signatures
1413
}
1514

1615
fn _g1((): ()) {}
1716

18-
1917
extern "C" fn _f2() {
2018
become _g2(); //~ error: mismatched function ABIs
2119
}
2220

2321
fn _g2() {}
2422

25-
2623
fn _f3() {
2724
become _g3(); //~ error: mismatched function ABIs
2825
}
2926

3027
extern "C" fn _g3() {}
3128

29+
extern "tail" fn _tailcc() {}
30+
31+
fn _f4() {
32+
// tailcc does not need the signatures to match,
33+
// but only tailcc can tail call tailcc.
34+
become _tailcc(); //~ error: mismatched function ABIs
35+
}
3236

3337
fn main() {}

0 commit comments

Comments
 (0)