Skip to content

Commit 15219a0

Browse files
committed
fix tail calls to #[track_caller] functions
1 parent 07b7dc9 commit 15219a0

File tree

9 files changed

+133
-8
lines changed

9 files changed

+133
-8
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3622,6 +3622,7 @@ dependencies = [
36223622
"rustc_hir",
36233623
"rustc_incremental",
36243624
"rustc_index",
3625+
"rustc_lint_defs",
36253626
"rustc_macros",
36263627
"rustc_metadata",
36273628
"rustc_middle",

compiler/rustc_codegen_ssa/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ rustc_hashes = { path = "../rustc_hashes" }
2626
rustc_hir = { path = "../rustc_hir" }
2727
rustc_incremental = { path = "../rustc_incremental" }
2828
rustc_index = { path = "../rustc_index" }
29+
rustc_lint_defs = { path = "../rustc_lint_defs" }
2930
rustc_macros = { path = "../rustc_macros" }
3031
rustc_metadata = { path = "../rustc_metadata" }
3132
rustc_middle = { path = "../rustc_middle" }

compiler/rustc_codegen_ssa/src/mir/block.rs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use rustc_ast as ast;
55
use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece};
66
use rustc_data_structures::packed::Pu128;
77
use rustc_hir::lang_items::LangItem;
8+
use rustc_lint_defs::builtin::TAIL_CALL_TRACK_CALLER;
89
use rustc_middle::mir::{self, AssertKind, InlineAsmMacro, SwitchTargets, UnwindTerminateReason};
910
use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf, ValidityRequirement};
1011
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> {
906907
fn_span,
907908
);
908909

909-
let instance = match instance.def {
910+
match instance.def {
910911
// We don't need AsyncDropGlueCtorShim here because it is not `noop func`,
911912
// it is `func returning noop future`
912913
ty::InstanceKind::DropGlue(_, None) => {
@@ -995,14 +996,35 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
995996
intrinsic.name,
996997
);
997998
}
998-
instance
999+
(Some(instance), None)
9991000
}
10001001
}
10011002
}
1002-
_ => instance,
1003-
};
10041003

1005-
(Some(instance), None)
1004+
_ if kind == CallKind::Tail
1005+
&& instance.def.requires_caller_location(bx.tcx()) =>
1006+
{
1007+
if let Some(hir_id) =
1008+
terminator.source_info.scope.lint_root(&self.mir.source_scopes)
1009+
{
1010+
let msg = "tail calling a function marked with `#[track_caller]` has no special effect";
1011+
bx.tcx().node_lint(TAIL_CALL_TRACK_CALLER, hir_id, |d| {
1012+
_ = d.primary_message(msg).span(fn_span)
1013+
});
1014+
}
1015+
1016+
let instance = ty::Instance::resolve_for_fn_ptr(
1017+
bx.tcx(),
1018+
bx.typing_env(),
1019+
def_id,
1020+
generic_args,
1021+
)
1022+
.unwrap();
1023+
1024+
(None, Some(bx.get_fn_addr(instance)))
1025+
}
1026+
_ => (Some(instance), None),
1027+
}
10061028
}
10071029
ty::FnPtr(..) => (None, Some(callee.immediate())),
10081030
_ => bug!("{} is not callable", callee.layout.ty),

compiler/rustc_lint_defs/src/builtin.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5100,3 +5100,35 @@ declare_lint! {
51005100
report_in_deps: true,
51015101
};
51025102
}
5103+
5104+
declare_lint! {
5105+
/// The `tail_call_track_caller` lint detects usage of `become` attempting to tail call
5106+
/// function marked with `#[track_caller]`.
5107+
///
5108+
/// ### Example
5109+
///
5110+
/// ```rust
5111+
/// #![feature(explicit_tail_calls)]
5112+
/// #![expect(incomplete_features)]
5113+
///
5114+
/// #[track_caller]
5115+
/// fn f() {}
5116+
///
5117+
/// fn g() {
5118+
/// become f();
5119+
/// }
5120+
///
5121+
/// g();
5122+
/// ```
5123+
///
5124+
/// {{produces}}
5125+
///
5126+
/// ### Explanation
5127+
///
5128+
/// Due to implementation details of tail calls and `#[track_caller]` attribute, calls to
5129+
/// functions marked with `#[track_caller]` cannot become tail calls. As such using `become`
5130+
/// is no different than a normal call (except for changes in drop order).
5131+
pub TAIL_CALL_TRACK_CALLER,
5132+
Warn,
5133+
"detects tail calls of functions marked with `#[track_caller]`"
5134+
}

compiler/rustc_monomorphize/src/collector.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -788,7 +788,35 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> {
788788
// *Before* monomorphizing, record that we already handled this mention.
789789
self.used_mentioned_items.insert(MentionedItem::Fn(callee_ty));
790790
let callee_ty = self.monomorphize(callee_ty);
791-
visit_fn_use(self.tcx, callee_ty, true, source, &mut self.used_items)
791+
792+
// HACK(explicit_tail_calls): collect tail calls to `#[track_caller]` functions as indirect,
793+
// because we later call them as such, to prevent issues with ABI incompatibility.
794+
// Ideally we'd replace such tail calls with normal call + return, but this requires
795+
// post-mono MIR optimizations, which we don't yet have.
796+
let force_indirect_call =
797+
if matches!(terminator.kind, mir::TerminatorKind::TailCall { .. })
798+
&& let &ty::FnDef(def_id, args) = callee_ty.kind()
799+
&& let instance = ty::Instance::expect_resolve(
800+
self.tcx,
801+
ty::TypingEnv::fully_monomorphized(),
802+
def_id,
803+
args,
804+
source,
805+
)
806+
&& instance.def.requires_caller_location(self.tcx)
807+
{
808+
true
809+
} else {
810+
false
811+
};
812+
813+
visit_fn_use(
814+
self.tcx,
815+
callee_ty,
816+
!force_indirect_call,
817+
source,
818+
&mut self.used_items,
819+
)
792820
}
793821
mir::TerminatorKind::Drop { ref place, .. } => {
794822
let ty = place.ty(self.body, self.tcx).ty;

tests/ui/explicit-tail-calls/callee_is_track_caller.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
//@ check-pass
2-
// FIXME(explicit_tail_calls): make this run-pass, once tail calls are properly implemented
1+
//@ run-pass
2+
//@ ignore-pass
33
#![expect(incomplete_features)]
44
#![feature(explicit_tail_calls)]
55

66
fn a(x: u32) -> u32 {
77
become b(x);
8+
//~^ warning: tail calling a function marked with `#[track_caller]` has no special effect
89
}
910

1011
#[track_caller]
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
warning: tail calling a function marked with `#[track_caller]` has no special effect
2+
--> $DIR/callee_is_track_caller.rs:7:12
3+
|
4+
LL | become b(x);
5+
| ^^^^
6+
|
7+
= note: `#[warn(tail_call_track_caller)]` on by default
8+
9+
warning: 1 warning emitted
10+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//@ run-pass
2+
//@ ignore-pass
3+
#![expect(incomplete_features)]
4+
#![feature(explicit_tail_calls)]
5+
6+
fn c<T: Trait>() {
7+
become T::f();
8+
//~^ warning: tail calling a function marked with `#[track_caller]` has no special effect
9+
}
10+
11+
trait Trait {
12+
#[track_caller]
13+
fn f() {}
14+
}
15+
16+
impl Trait for () {}
17+
18+
fn main() {
19+
c::<()>();
20+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
warning: tail calling a function marked with `#[track_caller]` has no special effect
2+
--> $DIR/callee_is_track_caller_polymorphic.rs:7:12
3+
|
4+
LL | become T::f();
5+
| ^^^^^^
6+
|
7+
= note: `#[warn(tail_call_track_caller)]` on by default
8+
9+
warning: 1 warning emitted
10+

0 commit comments

Comments
 (0)