diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index d4932adeda1f9..ab983ceb72ffc 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -1317,7 +1317,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { } LocalRef::Operand(arg) => { let Ref(place_value) = arg.val else { - bug!("only `Ref` should use `PassMode::Indirect`"); + bug!( + "only `Ref` should use `PassMode::Indirect`, but got {:?}", + arg.val + ); }; bx.typed_place_copy(place_value, tmp.val, fn_abi.args[i].layout); op.val = arg.val; diff --git a/compiler/rustc_hir_typeck/src/check.rs b/compiler/rustc_hir_typeck/src/check.rs index 5fe1f4191c93f..1780430567a80 100644 --- a/compiler/rustc_hir_typeck/src/check.rs +++ b/compiler/rustc_hir_typeck/src/check.rs @@ -90,7 +90,7 @@ pub(super) fn check_fn<'a, 'tcx>( } // Check that argument is Sized. - if !params_can_be_unsized { + if !params_can_be_unsized || fn_sig.abi() == rustc_abi::ExternAbi::RustTail { fcx.require_type_is_sized( param_ty, param.ty_span, diff --git a/compiler/rustc_mir_build/src/check_tail_calls.rs b/compiler/rustc_mir_build/src/check_tail_calls.rs index 8052ee26df841..ac9f6e384cf04 100644 --- a/compiler/rustc_mir_build/src/check_tail_calls.rs +++ b/compiler/rustc_mir_build/src/check_tail_calls.rs @@ -147,7 +147,9 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { // ``` // we should think what is the expected behavior here. // (we should probably just accept this by revealing opaques?) - if caller_sig.inputs_and_output != callee_sig.inputs_and_output { + if caller_sig.inputs_and_output != callee_sig.inputs_and_output + && !matches!(callee_sig.abi(), ExternAbi::RustTail) + { let caller_ty = self.tcx.type_of(self.caller_def_id).skip_binder(); self.report_signature_mismatch( @@ -189,6 +191,12 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { if callee_sig.c_variadic() { self.report_c_variadic_callee(expr.span); } + + for &arg_ty in callee_sig.inputs() { + if !arg_ty.is_sized(self.tcx, self.typing_env) { + self.report_unsized_argument(expr.span, arg_ty); + } + } } /// Returns true if the caller function needs a location argument @@ -417,6 +425,17 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { self.found_errors = Err(err); } + + fn report_unsized_argument(&mut self, sp: Span, arg_ty: Ty<'tcx>) { + let err = self + .tcx + .dcx() + .struct_span_err(sp, format!("unsized arguments cannot be used in a tail call")) + .with_note(format!("unsized argument of type `{arg_ty}`")) + .emit(); + + self.found_errors = Err(err); + } } impl<'a, 'tcx> Visitor<'a, 'tcx> for TailCallCkVisitor<'a, 'tcx> { diff --git a/compiler/rustc_target/src/callconv/mod.rs b/compiler/rustc_target/src/callconv/mod.rs index 7e1eafa10097f..30d16b5c7b1c9 100644 --- a/compiler/rustc_target/src/callconv/mod.rs +++ b/compiler/rustc_target/src/callconv/mod.rs @@ -827,6 +827,9 @@ impl<'a, Ty> FnAbi<'a, Ty> { ArgAttribute::default() }; arg.cast_to_with_attrs(Reg { kind: RegKind::Integer, size }, attr.into()); + } else if self.conv == CanonAbi::RustTail { + assert!(arg.layout.is_sized(), "extern \"tail\" arguments must be sized"); + arg.pass_by_stack_offset(None); } } diff --git a/compiler/rustc_ty_utils/src/abi.rs b/compiler/rustc_ty_utils/src/abi.rs index e782557d126bf..5c67021c59c59 100644 --- a/compiler/rustc_ty_utils/src/abi.rs +++ b/compiler/rustc_ty_utils/src/abi.rs @@ -458,8 +458,10 @@ fn fn_abi_sanity_check<'tcx>( // omitted entirely in the calling convention. assert!(arg.is_ignore()); } - if let PassMode::Indirect { on_stack, .. } = arg.mode { - assert!(!on_stack, "rust abi shouldn't use on_stack"); + if let PassMode::Indirect { on_stack, .. } = arg.mode + && spec_abi != ExternAbi::RustTail + { + assert!(!on_stack, "rustic abi {spec_abi:?} shouldn't use on_stack"); } } else if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { assert_matches!( diff --git a/tests/ui/explicit-tail-calls/no-unsized-arguments.aarch64.stderr b/tests/ui/explicit-tail-calls/no-unsized-arguments.aarch64.stderr new file mode 100644 index 0000000000000..33e190e1a4a7f --- /dev/null +++ b/tests/ui/explicit-tail-calls/no-unsized-arguments.aarch64.stderr @@ -0,0 +1,31 @@ +error[E0277]: the size for values of type `[u8]` cannot be known at compilation time + --> $DIR/no-unsized-arguments.rs:40:42 + | +LL | extern "tail" fn unsized_argument(x: [u8]) -> u8 { + | ^^^^ doesn't have a size known at compile-time + | + = help: the trait `Sized` is not implemented for `[u8]` +help: function arguments must have a statically known size, borrowed slices always have a known size + | +LL | extern "tail" fn unsized_argument(x: &[u8]) -> u8 { + | + + +error: unsized arguments cannot be used in a tail call + --> $DIR/no-unsized-arguments.rs:33:5 + | +LL | become unsized_argument(b); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: unsized argument of type `[u8]` + +error: unsized arguments cannot be used in a tail call + --> $DIR/no-unsized-arguments.rs:48:5 + | +LL | become unsized_argument(*b); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: unsized argument of type `[u8]` + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/explicit-tail-calls/no-unsized-arguments.rs b/tests/ui/explicit-tail-calls/no-unsized-arguments.rs new file mode 100644 index 0000000000000..a0c5b53d8df0c --- /dev/null +++ b/tests/ui/explicit-tail-calls/no-unsized-arguments.rs @@ -0,0 +1,50 @@ +//@ add-minicore +//@ ignore-backends: gcc +//@ min-llvm-version: 22 +// +//@ revisions: x86 x86_64 aarch64 +// +//@ [x86] compile-flags: --target=i686-unknown-linux-gnu +//@ [x86] needs-llvm-components: x86 +//@ [x86_64] compile-flags: --target=x86_64-unknown-linux-gnu +//@ [x86_64] needs-llvm-components: x86 +//@ [aarch64] compile-flags: --target=aarch64-unknown-linux-gnu +//@ [aarch64] needs-llvm-components: aarch64 +#![feature(explicit_tail_calls, rust_tail_cc, unsized_fn_params, no_core)] +#![allow(incomplete_features, internal_features)] +#![no_core] +#![crate_type = "lib"] + +extern crate minicore; +use minicore::*; + +extern "C" { + fn extract(_: [u8]) -> u8; +} + +fn vanilla(b: [u8]) -> u8 { + fn unsized_argument(x: [u8]) -> u8 { + unsafe { extract(x) } + } + + // Non-tail call. + let _ = unsized_argument(b); + + become unsized_argument(b); + //~^ ERROR unsized arguments cannot be used in a tail call +} + +extern "tail" fn tailcc(b: &[u8]) -> u8 { + // `extern "tail"` is special because we also can't unsized parameters in standard definitions + // and calls. + extern "tail" fn unsized_argument(x: [u8]) -> u8 { + //~^ ERROR the size for values of type `[u8]` cannot be known at compilation time + unsafe { extract(x) } + } + + // Vanilla call. + let _ = unsized_argument(*b); + + become unsized_argument(*b); + //~^ ERROR unsized arguments cannot be used in a tail call +} diff --git a/tests/ui/explicit-tail-calls/no-unsized-arguments.x86.stderr b/tests/ui/explicit-tail-calls/no-unsized-arguments.x86.stderr new file mode 100644 index 0000000000000..33e190e1a4a7f --- /dev/null +++ b/tests/ui/explicit-tail-calls/no-unsized-arguments.x86.stderr @@ -0,0 +1,31 @@ +error[E0277]: the size for values of type `[u8]` cannot be known at compilation time + --> $DIR/no-unsized-arguments.rs:40:42 + | +LL | extern "tail" fn unsized_argument(x: [u8]) -> u8 { + | ^^^^ doesn't have a size known at compile-time + | + = help: the trait `Sized` is not implemented for `[u8]` +help: function arguments must have a statically known size, borrowed slices always have a known size + | +LL | extern "tail" fn unsized_argument(x: &[u8]) -> u8 { + | + + +error: unsized arguments cannot be used in a tail call + --> $DIR/no-unsized-arguments.rs:33:5 + | +LL | become unsized_argument(b); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: unsized argument of type `[u8]` + +error: unsized arguments cannot be used in a tail call + --> $DIR/no-unsized-arguments.rs:48:5 + | +LL | become unsized_argument(*b); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: unsized argument of type `[u8]` + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/explicit-tail-calls/no-unsized-arguments.x86_64.stderr b/tests/ui/explicit-tail-calls/no-unsized-arguments.x86_64.stderr new file mode 100644 index 0000000000000..33e190e1a4a7f --- /dev/null +++ b/tests/ui/explicit-tail-calls/no-unsized-arguments.x86_64.stderr @@ -0,0 +1,31 @@ +error[E0277]: the size for values of type `[u8]` cannot be known at compilation time + --> $DIR/no-unsized-arguments.rs:40:42 + | +LL | extern "tail" fn unsized_argument(x: [u8]) -> u8 { + | ^^^^ doesn't have a size known at compile-time + | + = help: the trait `Sized` is not implemented for `[u8]` +help: function arguments must have a statically known size, borrowed slices always have a known size + | +LL | extern "tail" fn unsized_argument(x: &[u8]) -> u8 { + | + + +error: unsized arguments cannot be used in a tail call + --> $DIR/no-unsized-arguments.rs:33:5 + | +LL | become unsized_argument(b); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: unsized argument of type `[u8]` + +error: unsized arguments cannot be used in a tail call + --> $DIR/no-unsized-arguments.rs:48:5 + | +LL | become unsized_argument(*b); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: unsized argument of type `[u8]` + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/explicit-tail-calls/signature-mismatch.rs b/tests/ui/explicit-tail-calls/signature-mismatch.rs index a32ac9d8bfee3..bed480f60f63b 100644 --- a/tests/ui/explicit-tail-calls/signature-mismatch.rs +++ b/tests/ui/explicit-tail-calls/signature-mismatch.rs @@ -1,5 +1,5 @@ #![expect(incomplete_features)] -#![feature(explicit_tail_calls)] +#![feature(explicit_tail_calls, rust_tail_cc)] #![feature(c_variadic)] fn _f0((): ()) { @@ -8,26 +8,30 @@ fn _f0((): ()) { fn _g0() {} - fn _f1() { become _g1(()); //~ error: mismatched signatures } fn _g1((): ()) {} - extern "C" fn _f2() { become _g2(); //~ error: mismatched function ABIs } fn _g2() {} - fn _f3() { become _g3(); //~ error: mismatched function ABIs } extern "C" fn _g3() {} +extern "tail" fn _tailcc() {} + +fn _f4() { + // tailcc does not need the signatures to match, + // but only tailcc can tail call tailcc. + become _tailcc(); //~ error: mismatched function ABIs +} fn main() {} diff --git a/tests/ui/explicit-tail-calls/signature-mismatch.stderr b/tests/ui/explicit-tail-calls/signature-mismatch.stderr index ba9e9dcb98483..6e26f1c075539 100644 --- a/tests/ui/explicit-tail-calls/signature-mismatch.stderr +++ b/tests/ui/explicit-tail-calls/signature-mismatch.stderr @@ -9,7 +9,7 @@ LL | become _g0(); = note: callee signature: `fn()` error: mismatched signatures - --> $DIR/signature-mismatch.rs:13:5 + --> $DIR/signature-mismatch.rs:12:5 | LL | become _g1(()); | ^^^^^^^^^^^^^^ @@ -19,7 +19,7 @@ LL | become _g1(()); = note: callee signature: `fn(())` error: mismatched function ABIs - --> $DIR/signature-mismatch.rs:20:5 + --> $DIR/signature-mismatch.rs:18:5 | LL | become _g2(); | ^^^^^^^^^^^^ @@ -28,7 +28,7 @@ LL | become _g2(); = note: caller ABI is `"C"`, while callee ABI is `"Rust"` error: mismatched function ABIs - --> $DIR/signature-mismatch.rs:27:5 + --> $DIR/signature-mismatch.rs:24:5 | LL | become _g3(); | ^^^^^^^^^^^^ @@ -36,5 +36,14 @@ LL | become _g3(); = note: `become` requires caller and callee to have the same ABI = note: caller ABI is `"Rust"`, while callee ABI is `"C"` -error: aborting due to 4 previous errors +error: mismatched function ABIs + --> $DIR/signature-mismatch.rs:34:5 + | +LL | become _tailcc(); + | ^^^^^^^^^^^^^^^^ + | + = note: `become` requires caller and callee to have the same ABI + = note: caller ABI is `"Rust"`, while callee ABI is `"tail"` + +error: aborting due to 5 previous errors diff --git a/tests/ui/explicit-tail-calls/tailcc-no-signature-restriction.rs b/tests/ui/explicit-tail-calls/tailcc-no-signature-restriction.rs new file mode 100644 index 0000000000000..9c9085ca1daca --- /dev/null +++ b/tests/ui/explicit-tail-calls/tailcc-no-signature-restriction.rs @@ -0,0 +1,49 @@ +//@ run-pass +//@ ignore-backends: gcc +//@ min-llvm-version: 22 +//@ revisions: x86_64 aarch64 +// +// FIXME: enable x86 on LLVM 23. +//@ [x86_64] only-x86_64 +//@ [aarch64] only-aarch64 +#![feature(explicit_tail_calls, rust_tail_cc)] + +#[inline(never)] +pub extern "tail" fn add() -> u64 { + #[inline(never)] + extern "tail" fn add(a: u64, b: u64) -> u64 { + a.wrapping_add(b) + } + + become add(1, 2); +} + +#[inline(never)] +pub extern "tail" fn pass_struct(a: u64, d: u64) -> u64 { + #[derive(Clone, Copy)] + pub struct Large { + pub a: u64, + pub b: u64, + pub c: u64, + pub d: u64, + } + + #[inline(never)] + extern "tail" fn add(large: Large) -> u64 { + let _ = large.b; + let _ = large.c; + large.a.wrapping_add(large.d) + } + + let large = Large { a, b: 0xBBBB_BBBB_BBBB_BBBB, c: 0xCCCC_CCCC_CCCC_CCCC, d }; + become add(large); +} + +fn main() { + assert_eq!(add(), 3); + + // FIXME: LLVM 22 has a bug which makes this miscompile. + if false { + assert_eq!(pass_struct(5, 6), 5 + 6); + } +}