about summary refs log tree commit diff
diff options
context:
space:
mode:
authorWaffle Lapkin <waffle.lapkin@gmail.com>2025-08-03 17:03:25 +0200
committerWaffle Lapkin <waffle.lapkin@gmail.com>2025-08-13 02:26:52 +0200
commit85d1c89e0fe2c9db253c6e4e53a19302584a2dfd (patch)
treed1f50304add0dc7d890eac4d2831666c7b499c65
parent07b7dc90ee4df5815dbb91ef8e98cb93571230f5 (diff)
downloadrust-85d1c89e0fe2c9db253c6e4e53a19302584a2dfd.tar.gz
rust-85d1c89e0fe2c9db253c6e4e53a19302584a2dfd.zip
fix tail calls to `#[track_caller]` functions
-rw-r--r--Cargo.lock1
-rw-r--r--compiler/rustc_codegen_ssa/Cargo.toml1
-rw-r--r--compiler/rustc_codegen_ssa/src/mir/block.rs32
-rw-r--r--compiler/rustc_lint_defs/src/builtin.rs33
-rw-r--r--compiler/rustc_monomorphize/src/collector.rs30
-rw-r--r--tests/ui/explicit-tail-calls/callee_is_track_caller.rs5
-rw-r--r--tests/ui/explicit-tail-calls/callee_is_track_caller.stderr10
-rw-r--r--tests/ui/explicit-tail-calls/callee_is_track_caller_polymorphic.rs20
-rw-r--r--tests/ui/explicit-tail-calls/callee_is_track_caller_polymorphic.stderr10
9 files changed, 134 insertions, 8 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 5a3906c470f..8aa4196d6a8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3622,6 +3622,7 @@ dependencies = [
  "rustc_hir",
  "rustc_incremental",
  "rustc_index",
+ "rustc_lint_defs",
  "rustc_macros",
  "rustc_metadata",
  "rustc_middle",
diff --git a/compiler/rustc_codegen_ssa/Cargo.toml b/compiler/rustc_codegen_ssa/Cargo.toml
index 30956fd1855..6a8971de746 100644
--- a/compiler/rustc_codegen_ssa/Cargo.toml
+++ b/compiler/rustc_codegen_ssa/Cargo.toml
@@ -26,6 +26,7 @@ rustc_hashes = { path = "../rustc_hashes" }
 rustc_hir = { path = "../rustc_hir" }
 rustc_incremental = { path = "../rustc_incremental" }
 rustc_index = { path = "../rustc_index" }
+rustc_lint_defs = { path = "../rustc_lint_defs" }
 rustc_macros = { path = "../rustc_macros" }
 rustc_metadata = { path = "../rustc_metadata" }
 rustc_middle = { path = "../rustc_middle" }
diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs
index e96590441fa..f151e24947e 100644
--- a/compiler/rustc_codegen_ssa/src/mir/block.rs
+++ b/compiler/rustc_codegen_ssa/src/mir/block.rs
@@ -5,6 +5,7 @@ use rustc_ast as ast;
 use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece};
 use rustc_data_structures::packed::Pu128;
 use rustc_hir::lang_items::LangItem;
+use rustc_lint_defs::builtin::TAIL_CALL_TRACK_CALLER;
 use rustc_middle::mir::{self, AssertKind, InlineAsmMacro, SwitchTargets, UnwindTerminateReason};
 use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf, ValidityRequirement};
 use rustc_middle::ty::print::{with_no_trimmed_paths, with_no_visible_paths};
@@ -906,7 +907,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                     fn_span,
                 );
 
-                let instance = match instance.def {
+                match instance.def {
                     // We don't need AsyncDropGlueCtorShim here because it is not `noop func`,
                     // it is `func returning noop future`
                     ty::InstanceKind::DropGlue(_, None) => {
@@ -995,14 +996,35 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
                                         intrinsic.name,
                                     );
                                 }
-                                instance
+                                (Some(instance), None)
                             }
                         }
                     }
-                    _ => instance,
-                };
 
-                (Some(instance), None)
+                    _ if kind == CallKind::Tail
+                        && instance.def.requires_caller_location(bx.tcx()) =>
+                    {
+                        if let Some(hir_id) =
+                            terminator.source_info.scope.lint_root(&self.mir.source_scopes)
+                        {
+                            let msg = "tail calling a function marked with `#[track_caller]` has no special effect";
+                            bx.tcx().node_lint(TAIL_CALL_TRACK_CALLER, hir_id, |d| {
+                                _ = d.primary_message(msg).span(fn_span)
+                            });
+                        }
+
+                        let instance = ty::Instance::resolve_for_fn_ptr(
+                            bx.tcx(),
+                            bx.typing_env(),
+                            def_id,
+                            generic_args,
+                        )
+                        .unwrap();
+
+                        (None, Some(bx.get_fn_addr(instance)))
+                    }
+                    _ => (Some(instance), None),
+                }
             }
             ty::FnPtr(..) => (None, Some(callee.immediate())),
             _ => bug!("{} is not callable", callee.layout.ty),
diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs
index 3b84c6b6110..a258d26c817 100644
--- a/compiler/rustc_lint_defs/src/builtin.rs
+++ b/compiler/rustc_lint_defs/src/builtin.rs
@@ -5100,3 +5100,36 @@ declare_lint! {
         report_in_deps: true,
     };
 }
+
+declare_lint! {
+    /// The `tail_call_track_caller` lint detects usage of `become` attempting to tail call
+    /// a function marked with `#[track_caller]`.
+    ///
+    /// ### Example
+    ///
+    /// ```rust
+    /// #![feature(explicit_tail_calls)]
+    /// #![expect(incomplete_features)]
+    ///
+    /// #[track_caller]
+    /// fn f() {}
+    ///
+    /// fn g() {
+    ///     become f();
+    /// }
+    ///
+    /// g();
+    /// ```
+    ///
+    /// {{produces}}
+    ///
+    /// ### Explanation
+    ///
+    /// Due to implementation details of tail calls and `#[track_caller]` attribute, calls to
+    /// functions marked with `#[track_caller]` cannot become tail calls. As such using `become`
+    /// is no different than a normal call (except for changes in drop order).
+    pub TAIL_CALL_TRACK_CALLER,
+    Warn,
+    "detects tail calls of functions marked with `#[track_caller]`",
+    @feature_gate = explicit_tail_calls;
+}
diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs
index 35b80a9b96f..5c94242b450 100644
--- a/compiler/rustc_monomorphize/src/collector.rs
+++ b/compiler/rustc_monomorphize/src/collector.rs
@@ -788,7 +788,35 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> {
                 // *Before* monomorphizing, record that we already handled this mention.
                 self.used_mentioned_items.insert(MentionedItem::Fn(callee_ty));
                 let callee_ty = self.monomorphize(callee_ty);
-                visit_fn_use(self.tcx, callee_ty, true, source, &mut self.used_items)
+
+                // HACK(explicit_tail_calls): collect tail calls to `#[track_caller]` functions as indirect,
+                // because we later call them as such, to prevent issues with ABI incompatibility.
+                // Ideally we'd replace such tail calls with normal call + return, but this requires
+                // post-mono MIR optimizations, which we don't yet have.
+                let force_indirect_call =
+                    if matches!(terminator.kind, mir::TerminatorKind::TailCall { .. })
+                        && let &ty::FnDef(def_id, args) = callee_ty.kind()
+                        && let instance = ty::Instance::expect_resolve(
+                            self.tcx,
+                            ty::TypingEnv::fully_monomorphized(),
+                            def_id,
+                            args,
+                            source,
+                        )
+                        && instance.def.requires_caller_location(self.tcx)
+                    {
+                        true
+                    } else {
+                        false
+                    };
+
+                visit_fn_use(
+                    self.tcx,
+                    callee_ty,
+                    !force_indirect_call,
+                    source,
+                    &mut self.used_items,
+                )
             }
             mir::TerminatorKind::Drop { ref place, .. } => {
                 let ty = place.ty(self.body, self.tcx).ty;
diff --git a/tests/ui/explicit-tail-calls/callee_is_track_caller.rs b/tests/ui/explicit-tail-calls/callee_is_track_caller.rs
index bcb93fda8c8..b85b335844b 100644
--- a/tests/ui/explicit-tail-calls/callee_is_track_caller.rs
+++ b/tests/ui/explicit-tail-calls/callee_is_track_caller.rs
@@ -1,10 +1,11 @@
-//@ check-pass
-// FIXME(explicit_tail_calls): make this run-pass, once tail calls are properly implemented
+//@ run-pass
+//@ ignore-pass
 #![expect(incomplete_features)]
 #![feature(explicit_tail_calls)]
 
 fn a(x: u32) -> u32 {
     become b(x);
+    //~^ warning: tail calling a function marked with `#[track_caller]` has no special effect
 }
 
 #[track_caller]
diff --git a/tests/ui/explicit-tail-calls/callee_is_track_caller.stderr b/tests/ui/explicit-tail-calls/callee_is_track_caller.stderr
new file mode 100644
index 00000000000..e1a251d156f
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/callee_is_track_caller.stderr
@@ -0,0 +1,10 @@
+warning: tail calling a function marked with `#[track_caller]` has no special effect
+  --> $DIR/callee_is_track_caller.rs:7:12
+   |
+LL |     become b(x);
+   |            ^^^^
+   |
+   = note: `#[warn(tail_call_track_caller)]` on by default
+
+warning: 1 warning emitted
+
diff --git a/tests/ui/explicit-tail-calls/callee_is_track_caller_polymorphic.rs b/tests/ui/explicit-tail-calls/callee_is_track_caller_polymorphic.rs
new file mode 100644
index 00000000000..33384de83eb
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/callee_is_track_caller_polymorphic.rs
@@ -0,0 +1,20 @@
+//@ run-pass
+//@ ignore-pass
+#![expect(incomplete_features)]
+#![feature(explicit_tail_calls)]
+
+fn c<T: Trait>() {
+    become T::f();
+    //~^ warning: tail calling a function marked with `#[track_caller]` has no special effect
+}
+
+trait Trait {
+    #[track_caller]
+    fn f() {}
+}
+
+impl Trait for () {}
+
+fn main() {
+    c::<()>();
+}
diff --git a/tests/ui/explicit-tail-calls/callee_is_track_caller_polymorphic.stderr b/tests/ui/explicit-tail-calls/callee_is_track_caller_polymorphic.stderr
new file mode 100644
index 00000000000..5a1c40509ad
--- /dev/null
+++ b/tests/ui/explicit-tail-calls/callee_is_track_caller_polymorphic.stderr
@@ -0,0 +1,10 @@
+warning: tail calling a function marked with `#[track_caller]` has no special effect
+  --> $DIR/callee_is_track_caller_polymorphic.rs:7:12
+   |
+LL |     become T::f();
+   |            ^^^^^^
+   |
+   = note: `#[warn(tail_call_track_caller)]` on by default
+
+warning: 1 warning emitted
+