Skip to content

Commit ce0a4ed

Browse files
committed
Lift the same-signature restriction for extern "tail"
1 parent b998449 commit ce0a4ed

8 files changed

Lines changed: 188 additions & 6 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: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
131131
let callee_sig =
132132
self.tcx.normalize_erasing_late_bound_regions(self.typing_env, ty.fn_sig(self.tcx));
133133

134-
if caller_sig.abi() != callee_sig.abi() {
134+
if caller_sig.abi() != callee_sig.abi() && !matches!(callee_sig.abi(), ExternAbi::RustTail)
135+
{
135136
self.report_abi_mismatch(expr.span, caller_sig.abi(), callee_sig.abi());
136137
}
137138

@@ -147,7 +148,9 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
147148
// ```
148149
// we should think what is the expected behavior here.
149150
// (we should probably just accept this by revealing opaques?)
150-
if caller_sig.inputs_and_output != callee_sig.inputs_and_output {
151+
if caller_sig.inputs_and_output != callee_sig.inputs_and_output
152+
&& !matches!(callee_sig.abi(), ExternAbi::RustTail)
153+
{
151154
let caller_ty = self.tcx.type_of(self.caller_def_id).skip_binder();
152155

153156
self.report_signature_mismatch(
@@ -189,6 +192,12 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
189192
if callee_sig.c_variadic() {
190193
self.report_c_variadic_callee(expr.span);
191194
}
195+
196+
for &arg_ty in callee_sig.inputs() {
197+
if !arg_ty.is_sized(self.tcx, self.typing_env) {
198+
self.report_unsized_argument(expr.span, arg_ty);
199+
}
200+
}
192201
}
193202

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

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

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

compiler/rustc_target/src/callconv/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,10 @@ impl<'a, Ty> FnAbi<'a, Ty> {
828828
};
829829
arg.cast_to_with_attrs(Reg { kind: RegKind::Integer, size }, attr.into());
830830
}
831+
832+
if self.conv == CanonAbi::RustTail && arg.layout.is_sized() {
833+
arg.pass_by_stack_offset(None);
834+
}
831835
}
832836

833837
BackendRepr::SimdVector { .. } => {

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: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//@ check-fail
2+
//@ min-llvm-version: 22
3+
//
4+
//@ revisions: x86 x86_64 aarch64
5+
//
6+
//@ [x86] only-x86
7+
//@ [x86_64] only-x86_64
8+
//@ [aarch64] only-aarch64
9+
#![feature(explicit_tail_calls, rust_tail_cc, unsized_fn_params)]
10+
#![allow(incomplete_features, internal_features)]
11+
#![crate_type = "lib"]
12+
13+
mod vanilla {
14+
fn unsized_argument(x: [u8]) -> u8 {
15+
x[0]
16+
}
17+
18+
pub fn caller(_: [u8]) -> u8 {
19+
let b: Box<[u8]> = Box::new([1, 2, 3]);
20+
21+
// Non-tail call.
22+
let _ = unsized_argument(*b);
23+
24+
become unsized_argument(*b);
25+
//~^ ERROR unsized arguments cannot be used in a tail call
26+
}
27+
}
28+
29+
mod tailcc {
30+
// `extern "tail"` is special because we also can't unsized parameters in standard definitions
31+
// and calls.
32+
extern "tail" fn unsized_argument(x: [u8]) -> u8 {
33+
//~^ ERROR the size for values of type `[u8]` cannot be known at compilation time
34+
x[0]
35+
}
36+
37+
pub fn extern_rust_caller() -> u8 {
38+
let b: Box<[u8]> = Box::new([1, 2, 3]);
39+
40+
// Non-tail call.
41+
let _ = unsized_argument(*b);
42+
43+
become unsized_argument(*b);
44+
//~^ ERROR unsized arguments cannot be used in a tail call
45+
}
46+
47+
pub extern "tail" fn extern_tail_caller() -> u8 {
48+
let b: Box<[u8]> = Box::new([1, 2, 3]);
49+
50+
// Vanilla call.
51+
let _ = unsized_argument(*b);
52+
53+
become unsized_argument(*b);
54+
//~^ ERROR unsized arguments cannot be used in a tail call
55+
}
56+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
error[E0277]: the size for values of type `[u8]` cannot be known at compilation time
2+
--> $DIR/no-unsized-arguments.rs:32: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:24:9
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:43:9
23+
|
24+
LL | become unsized_argument(*b);
25+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
26+
|
27+
= note: unsized argument of type `[u8]`
28+
29+
error: unsized arguments cannot be used in a tail call
30+
--> $DIR/no-unsized-arguments.rs:53:9
31+
|
32+
LL | become unsized_argument(*b);
33+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
34+
|
35+
= note: unsized argument of type `[u8]`
36+
37+
error: aborting due to 4 previous errors
38+
39+
For more information about this error, try `rustc --explain E0277`.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//@ run-pass
2+
//@ min-llvm-version: 22
3+
//@ revisions: x86 x86_64 aarch64
4+
//
5+
//@ [x86] only-x86
6+
//@ [x86_64] only-x86_64
7+
//@ [aarch64] only-aarch64
8+
#![feature(explicit_tail_calls, rust_tail_cc)]
9+
10+
mod add {
11+
pub fn extern_rust_caller() -> u64 {
12+
become add(1, 2);
13+
}
14+
15+
pub extern "tail" fn extern_tail_caller() -> u64 {
16+
become add(1, 2);
17+
}
18+
19+
#[inline(never)]
20+
extern "tail" fn add(a: u64, b: u64) -> u64 {
21+
a.wrapping_add(b)
22+
}
23+
}
24+
25+
mod pass_struct {
26+
#[derive(Clone, Copy)]
27+
pub struct Large {
28+
pub a: u64,
29+
pub b: u64,
30+
pub c: u64,
31+
pub d: u64,
32+
}
33+
34+
pub fn extern_rust_caller(a: u64, d: u64) -> u64 {
35+
let large = Large { a, b: 0xBBBB_BBBB_BBBB_BBBB, c: 0xCCCC_CCCC_CCCC_CCCC, d };
36+
become add(large);
37+
}
38+
39+
pub extern "tail" fn extern_tail_caller(a: u64, d: u64) -> u64 {
40+
let large = Large { a, b: 0xBBBB_BBBB_BBBB_BBBB, c: 0xCCCC_CCCC_CCCC_CCCC, d };
41+
become add(large);
42+
}
43+
44+
#[inline(never)]
45+
extern "tail" fn add(large: Large) -> u64 {
46+
let _ = large.b;
47+
let _ = large.c;
48+
large.a.wrapping_add(large.d)
49+
}
50+
}
51+
52+
fn main() {
53+
assert_eq!(add::extern_rust_caller(), 3);
54+
assert_eq!(add::extern_tail_caller(), 3);
55+
56+
assert_eq!(pass_struct::extern_rust_caller(5, 6), 5 + 6);
57+
assert_eq!(pass_struct::extern_tail_caller(5, 6), 5 + 6);
58+
}

0 commit comments

Comments
 (0)