Skip to content

Commit cc99f2a

Browse files
committed
Incrementally track which frame to use for diagnostics
1 parent 3162b7a commit cc99f2a

File tree

6 files changed

+137
-160
lines changed

6 files changed

+137
-160
lines changed

src/concurrency/thread.rs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,12 @@ pub struct Thread<'mir, 'tcx> {
118118
/// The virtual call stack.
119119
stack: Vec<Frame<'mir, 'tcx, Provenance, FrameData<'tcx>>>,
120120

121+
/// The index of the topmost user-relevant frame in `stack`. This field must contain
122+
/// the value produced by `get_top_user_relevant_frame`.
123+
/// This field is a cache to reduce how often we call that method. The cache is manually
124+
/// maintained inside `MiriMachine::after_stack_push` and `MiriMachine::after_stack_pop`.
125+
top_user_relevant_frame: Option<usize>,
126+
121127
/// The join status.
122128
join_status: ThreadJoinStatus,
123129

@@ -147,6 +153,28 @@ impl<'mir, 'tcx> Thread<'mir, 'tcx> {
147153
fn thread_name(&self) -> &[u8] {
148154
if let Some(ref thread_name) = self.thread_name { thread_name } else { b"<unnamed>" }
149155
}
156+
157+
fn get_top_user_relevant_frame(&self) -> Option<usize> {
158+
self.stack
159+
.iter()
160+
.enumerate()
161+
.rev()
162+
.find_map(|(idx, frame)| if frame.extra.is_user_relevant { Some(idx) } else { None })
163+
}
164+
165+
pub fn recompute_top_user_relevant_frame(&mut self) {
166+
self.top_user_relevant_frame = self.get_top_user_relevant_frame();
167+
}
168+
169+
pub fn set_top_user_relevant_frame(&mut self, frame_idx: usize) {
170+
debug_assert_eq!(Some(frame_idx), self.get_top_user_relevant_frame());
171+
self.top_user_relevant_frame = Some(frame_idx);
172+
}
173+
174+
pub fn top_user_relevant_frame(&self) -> usize {
175+
debug_assert_eq!(self.top_user_relevant_frame, self.get_top_user_relevant_frame());
176+
self.top_user_relevant_frame.unwrap_or_else(|| self.stack.len().saturating_sub(1))
177+
}
150178
}
151179

152180
impl<'mir, 'tcx> std::fmt::Debug for Thread<'mir, 'tcx> {
@@ -167,6 +195,7 @@ impl<'mir, 'tcx> Default for Thread<'mir, 'tcx> {
167195
state: ThreadState::Enabled,
168196
thread_name: None,
169197
stack: Vec::new(),
198+
top_user_relevant_frame: None,
170199
join_status: ThreadJoinStatus::Joinable,
171200
panic_payload: None,
172201
last_error: None,
@@ -184,8 +213,15 @@ impl<'mir, 'tcx> Thread<'mir, 'tcx> {
184213

185214
impl VisitTags for Thread<'_, '_> {
186215
fn visit_tags(&self, visit: &mut dyn FnMut(SbTag)) {
187-
let Thread { panic_payload, last_error, stack, state: _, thread_name: _, join_status: _ } =
188-
self;
216+
let Thread {
217+
panic_payload,
218+
last_error,
219+
stack,
220+
top_user_relevant_frame: _,
221+
state: _,
222+
thread_name: _,
223+
join_status: _,
224+
} = self;
189225

190226
panic_payload.visit_tags(visit);
191227
last_error.visit_tags(visit);
@@ -414,7 +450,7 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
414450
}
415451

416452
/// Get a shared borrow of the currently active thread.
417-
fn active_thread_ref(&self) -> &Thread<'mir, 'tcx> {
453+
pub fn active_thread_ref(&self) -> &Thread<'mir, 'tcx> {
418454
&self.threads[self.active_thread]
419455
}
420456

src/helpers.rs

Lines changed: 19 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -936,78 +936,42 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
936936
}
937937

938938
impl<'mir, 'tcx> MiriMachine<'mir, 'tcx> {
939-
pub fn current_span(&self) -> CurrentSpan<'_, 'mir, 'tcx> {
940-
CurrentSpan { current_frame_idx: None, machine: self }
941-
}
942-
}
943-
944-
/// A `CurrentSpan` should be created infrequently (ideally once) per interpreter step. It does
945-
/// nothing on creation, but when `CurrentSpan::get` is called, searches the current stack for the
946-
/// topmost frame which corresponds to a local crate, and returns the current span in that frame.
947-
/// The result of that search is cached so that later calls are approximately free.
948-
#[derive(Clone)]
949-
pub struct CurrentSpan<'a, 'mir, 'tcx> {
950-
current_frame_idx: Option<usize>,
951-
machine: &'a MiriMachine<'mir, 'tcx>,
952-
}
953-
954-
impl<'a, 'mir: 'a, 'tcx: 'a + 'mir> CurrentSpan<'a, 'mir, 'tcx> {
955-
pub fn machine(&self) -> &'a MiriMachine<'mir, 'tcx> {
956-
self.machine
957-
}
958-
959-
/// Get the current span, skipping non-local frames.
939+
/// Get the current span in the topmost function which is workspace-local and not
940+
/// `#[track_caller]`.
960941
/// This function is backed by a cache, and can be assumed to be very fast.
961-
pub fn get(&mut self) -> Span {
962-
let idx = self.current_frame_idx();
963-
self.stack().get(idx).map(Frame::current_span).unwrap_or(rustc_span::DUMMY_SP)
942+
pub fn current_span(&self) -> Span {
943+
self.stack()
944+
.get(self.top_user_relevant_frame())
945+
.map(Frame::current_span)
946+
.unwrap_or(rustc_span::DUMMY_SP)
964947
}
965948

966949
/// Returns the span of the *caller* of the current operation, again
967950
/// walking down the stack to find the closest frame in a local crate, if the caller of the
968951
/// current operation is not in a local crate.
969952
/// This is useful when we are processing something which occurs on function-entry and we want
970953
/// to point at the call to the function, not the function definition generally.
971-
pub fn get_caller(&mut self) -> Span {
954+
pub fn caller_span(&self) -> Span {
972955
// We need to go down at least to the caller (len - 2), or however
973-
// far we have to go to find a frame in a local crate.
974-
let local_frame_idx = self.current_frame_idx();
956+
// far we have to go to find a frame in a local crate which is also not #[track_caller].
957+
let frame_idx = self.top_user_relevant_frame();
975958
let stack = self.stack();
976-
let idx = cmp::min(local_frame_idx, stack.len().saturating_sub(2));
977-
stack.get(idx).map(Frame::current_span).unwrap_or(rustc_span::DUMMY_SP)
959+
let frame_idx = cmp::min(frame_idx, stack.len().saturating_sub(2));
960+
stack.get(frame_idx).map(Frame::current_span).unwrap_or(rustc_span::DUMMY_SP)
978961
}
979962

980963
fn stack(&self) -> &[Frame<'mir, 'tcx, Provenance, machine::FrameData<'tcx>>] {
981-
self.machine.threads.active_thread_stack()
964+
self.threads.active_thread_stack()
982965
}
983966

984-
fn current_frame_idx(&mut self) -> usize {
985-
*self
986-
.current_frame_idx
987-
.get_or_insert_with(|| Self::compute_current_frame_index(self.machine))
967+
fn top_user_relevant_frame(&self) -> usize {
968+
self.threads.active_thread_ref().top_user_relevant_frame()
988969
}
989970

990-
// Find the position of the inner-most frame which is part of the crate being
991-
// compiled/executed, part of the Cargo workspace, and is also not #[track_caller].
992-
#[inline(never)]
993-
fn compute_current_frame_index(machine: &MiriMachine<'_, '_>) -> usize {
994-
machine
995-
.threads
996-
.active_thread_stack()
997-
.iter()
998-
.enumerate()
999-
.rev()
1000-
.find_map(|(idx, frame)| {
1001-
let def_id = frame.instance.def_id();
1002-
if (def_id.is_local() || machine.local_crates.contains(&def_id.krate))
1003-
&& !frame.instance.def.requires_caller_location(machine.tcx)
1004-
{
1005-
Some(idx)
1006-
} else {
1007-
None
1008-
}
1009-
})
1010-
.unwrap_or(0)
971+
pub fn is_user_relevant(&self, frame: &Frame<'mir, 'tcx, Provenance>) -> bool {
972+
let def_id = frame.instance.def_id();
973+
(def_id.is_local() || self.local_crates.contains(&def_id.krate))
974+
&& !frame.instance.def.requires_caller_location(self.tcx)
1011975
}
1012976
}
1013977

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ pub use crate::diagnostics::{
9797
pub use crate::eval::{
9898
create_ecx, eval_entry, AlignmentCheck, BacktraceStyle, IsolatedOp, MiriConfig, RejectOpWith,
9999
};
100-
pub use crate::helpers::{CurrentSpan, EvalContextExt as _};
100+
pub use crate::helpers::EvalContextExt as _;
101101
pub use crate::intptrcast::ProvenanceMode;
102102
pub use crate::machine::{
103103
AllocExtra, FrameData, MiriInterpCx, MiriInterpCxExt, MiriMachine, MiriMemoryKind,

src/machine.rs

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,18 @@ pub struct FrameData<'tcx> {
5050
/// for the start of this frame. When we finish executing this frame,
5151
/// we use this to register a completed event with `measureme`.
5252
pub timing: Option<measureme::DetachedTiming>,
53+
54+
/// Indicates whether a `Frame` is part of a workspace-local crate and is also not
55+
/// `#[track_caller]`. We compute this once on creation and store the result, as an
56+
/// optimization.
57+
/// This is used by `MiriMachine::current_span` and `MiriMachine::caller_span`
58+
pub is_user_relevant: bool,
5359
}
5460

5561
impl<'tcx> std::fmt::Debug for FrameData<'tcx> {
5662
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5763
// Omitting `timing`, it does not support `Debug`.
58-
let FrameData { stacked_borrows, catch_unwind, timing: _ } = self;
64+
let FrameData { stacked_borrows, catch_unwind, timing: _, is_user_relevant: _ } = self;
5965
f.debug_struct("FrameData")
6066
.field("stacked_borrows", stacked_borrows)
6167
.field("catch_unwind", catch_unwind)
@@ -65,7 +71,7 @@ impl<'tcx> std::fmt::Debug for FrameData<'tcx> {
6571

6672
impl VisitTags for FrameData<'_> {
6773
fn visit_tags(&self, visit: &mut dyn FnMut(SbTag)) {
68-
let FrameData { catch_unwind, stacked_borrows, timing: _ } = self;
74+
let FrameData { catch_unwind, stacked_borrows, timing: _, is_user_relevant: _ } = self;
6975

7076
catch_unwind.visit_tags(visit);
7177
stacked_borrows.visit_tags(visit);
@@ -895,13 +901,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
895901

896902
let alloc = alloc.into_owned();
897903
let stacks = ecx.machine.stacked_borrows.as_ref().map(|stacked_borrows| {
898-
Stacks::new_allocation(
899-
id,
900-
alloc.size(),
901-
stacked_borrows,
902-
kind,
903-
ecx.machine.current_span(),
904-
)
904+
Stacks::new_allocation(id, alloc.size(), stacked_borrows, kind, &ecx.machine)
905905
});
906906
let race_alloc = ecx.machine.data_race.as_ref().map(|data_race| {
907907
data_race::AllocExtra::new_allocation(
@@ -1016,8 +1016,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
10161016
prov_extra,
10171017
range,
10181018
machine.stacked_borrows.as_ref().unwrap(),
1019-
machine.current_span(),
1020-
&machine.threads,
1019+
machine,
10211020
)?;
10221021
}
10231022
if let Some(weak_memory) = &alloc_extra.weak_memory {
@@ -1048,8 +1047,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
10481047
prov_extra,
10491048
range,
10501049
machine.stacked_borrows.as_ref().unwrap(),
1051-
machine.current_span(),
1052-
&machine.threads,
1050+
machine,
10531051
)?;
10541052
}
10551053
if let Some(weak_memory) = &alloc_extra.weak_memory {
@@ -1083,8 +1081,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
10831081
prove_extra,
10841082
range,
10851083
machine.stacked_borrows.as_ref().unwrap(),
1086-
machine.current_span(),
1087-
&machine.threads,
1084+
machine,
10881085
)
10891086
} else {
10901087
Ok(())
@@ -1126,7 +1123,9 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
11261123
stacked_borrows: stacked_borrows.map(|sb| sb.borrow_mut().new_frame(&ecx.machine)),
11271124
catch_unwind: None,
11281125
timing,
1126+
is_user_relevant: ecx.machine.is_user_relevant(&frame),
11291127
};
1128+
11301129
Ok(frame.with_extra(extra))
11311130
}
11321131

@@ -1174,6 +1173,13 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
11741173

11751174
#[inline(always)]
11761175
fn after_stack_push(ecx: &mut InterpCx<'mir, 'tcx, Self>) -> InterpResult<'tcx> {
1176+
if ecx.frame().extra.is_user_relevant {
1177+
// We just pushed a local frame, so we know that the topmost local frame is the topmost
1178+
// frame. If we push a non-local frame, there's no need to do anything.
1179+
let stack_len = ecx.active_thread_stack().len();
1180+
ecx.active_thread_mut().set_top_user_relevant_frame(stack_len - 1);
1181+
}
1182+
11771183
if ecx.machine.stacked_borrows.is_some() { ecx.retag_return_place() } else { Ok(()) }
11781184
}
11791185

@@ -1183,6 +1189,11 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
11831189
mut frame: Frame<'mir, 'tcx, Provenance, FrameData<'tcx>>,
11841190
unwinding: bool,
11851191
) -> InterpResult<'tcx, StackPopJump> {
1192+
if frame.extra.is_user_relevant {
1193+
// All that we store is whether or not the frame we just removed is local, so now we
1194+
// have no idea where the next topmost local frame is. So we recompute it.
1195+
ecx.active_thread_mut().recompute_top_user_relevant_frame();
1196+
}
11861197
let timing = frame.extra.timing.take();
11871198
if let Some(stacked_borrows) = &ecx.machine.stacked_borrows {
11881199
stacked_borrows.borrow_mut().end_call(&frame.extra);

0 commit comments

Comments
 (0)