Skip to content

Commit 90118a1

Browse files
committed
Auto merge of #2697 - Vanille-N:borrow-tracking, r=RalfJung
Reorganizing `stacked_borrows` in anticipation of a different model These commits reorganize all the code inside the former `stacked_borrows` module and extract the part that really is specific to Stacked Borrows inside the `borrow_tracker/stacked_borrows` submodule. Everything not specific to SB is put in `borrow_tracker/mod.rs`. This is so that the future Tree Borrows model can be later added as a second submodule and reuse all the contents of `borrow_tracker/mod.rs`. This reorganization is accompanied by renamings, mostly from "stacked borrows" to "borrow tracking".
2 parents 4a12a13 + ab08f2a commit 90118a1

25 files changed

+608
-434
lines changed

src/tools/miri/src/bin/miri.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ fn main() {
317317
} else if arg == "-Zmiri-disable-validation" {
318318
miri_config.validate = false;
319319
} else if arg == "-Zmiri-disable-stacked-borrows" {
320-
miri_config.stacked_borrows = false;
320+
miri_config.borrow_tracker = None;
321321
} else if arg == "-Zmiri-disable-data-race-detector" {
322322
miri_config.data_race_detector = false;
323323
miri_config.weak_memory_emulation = false;
@@ -413,7 +413,7 @@ fn main() {
413413
err
414414
),
415415
};
416-
for id in ids.into_iter().map(miri::SbTag::new) {
416+
for id in ids.into_iter().map(miri::BorTag::new) {
417417
if let Some(id) = id {
418418
miri_config.tracked_pointer_tags.insert(id);
419419
} else {
Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
use std::cell::RefCell;
2+
use std::fmt;
3+
use std::num::NonZeroU64;
4+
5+
use log::trace;
6+
use smallvec::SmallVec;
7+
8+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
9+
use rustc_middle::mir::RetagKind;
10+
use rustc_target::abi::Size;
11+
12+
use crate::*;
13+
pub mod stacked_borrows;
14+
use stacked_borrows::diagnostics::RetagCause;
15+
16+
pub type CallId = NonZeroU64;
17+
18+
/// Tracking pointer provenance
19+
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
20+
pub struct BorTag(NonZeroU64);
21+
22+
impl BorTag {
23+
pub fn new(i: u64) -> Option<Self> {
24+
NonZeroU64::new(i).map(BorTag)
25+
}
26+
27+
pub fn get(&self) -> u64 {
28+
self.0.get()
29+
}
30+
31+
pub fn inner(&self) -> NonZeroU64 {
32+
self.0
33+
}
34+
35+
pub fn succ(self) -> Option<Self> {
36+
self.0.checked_add(1).map(Self)
37+
}
38+
39+
/// The minimum representable tag
40+
pub fn one() -> Self {
41+
Self::new(1).unwrap()
42+
}
43+
}
44+
45+
impl std::default::Default for BorTag {
46+
/// The default to be used when borrow tracking is disabled
47+
fn default() -> Self {
48+
Self::one()
49+
}
50+
}
51+
52+
impl fmt::Debug for BorTag {
53+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54+
write!(f, "<{}>", self.0)
55+
}
56+
}
57+
58+
/// Per-frame data for borrow tracking
59+
#[derive(Debug)]
60+
pub struct FrameExtra {
61+
/// The ID of the call this frame corresponds to.
62+
pub call_id: CallId,
63+
64+
/// If this frame is protecting any tags, they are listed here. We use this list to do
65+
/// incremental updates of the global list of protected tags stored in the
66+
/// `stacked_borrows::GlobalState` upon function return, and if we attempt to pop a protected
67+
/// tag, to identify which call is responsible for protecting the tag.
68+
/// See `Stack::item_popped` for more explanation.
69+
///
70+
/// This will contain one tag per reference passed to the function, so
71+
/// a size of 2 is enough for the vast majority of functions.
72+
pub protected_tags: SmallVec<[BorTag; 2]>,
73+
}
74+
75+
impl VisitTags for FrameExtra {
76+
fn visit_tags(&self, _visit: &mut dyn FnMut(BorTag)) {
77+
// `protected_tags` are fine to GC.
78+
}
79+
}
80+
81+
/// Extra global state, available to the memory access hooks.
82+
#[derive(Debug)]
83+
pub struct GlobalStateInner {
84+
/// Borrow tracker method currently in use.
85+
pub borrow_tracker_method: BorrowTrackerMethod,
86+
/// Next unused pointer ID (tag).
87+
pub next_ptr_tag: BorTag,
88+
/// Table storing the "base" tag for each allocation.
89+
/// The base tag is the one used for the initial pointer.
90+
/// We need this in a separate table to handle cyclic statics.
91+
pub base_ptr_tags: FxHashMap<AllocId, BorTag>,
92+
/// Next unused call ID (for protectors).
93+
pub next_call_id: CallId,
94+
/// All currently protected tags.
95+
/// An item is protected if its tag is in this set, *and* it has the "protected" bit set.
96+
/// We add tags to this when they are created with a protector in `reborrow`, and
97+
/// we remove tags from this when the call which is protecting them returns, in
98+
/// `GlobalStateInner::end_call`. See `Stack::item_popped` for more details.
99+
pub protected_tags: FxHashMap<BorTag, ProtectorKind>,
100+
/// The pointer ids to trace
101+
pub tracked_pointer_tags: FxHashSet<BorTag>,
102+
/// The call ids to trace
103+
pub tracked_call_ids: FxHashSet<CallId>,
104+
/// Whether to recurse into datatypes when searching for pointers to retag.
105+
pub retag_fields: RetagFields,
106+
}
107+
108+
impl VisitTags for GlobalStateInner {
109+
fn visit_tags(&self, _visit: &mut dyn FnMut(BorTag)) {
110+
// The only candidate is base_ptr_tags, and that does not need visiting since we don't ever
111+
// GC the bottommost tag.
112+
}
113+
}
114+
115+
/// We need interior mutable access to the global state.
116+
pub type GlobalState = RefCell<GlobalStateInner>;
117+
118+
/// Indicates which kind of access is being performed.
119+
#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)]
120+
pub enum AccessKind {
121+
Read,
122+
Write,
123+
}
124+
125+
impl fmt::Display for AccessKind {
126+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127+
match self {
128+
AccessKind::Read => write!(f, "read access"),
129+
AccessKind::Write => write!(f, "write access"),
130+
}
131+
}
132+
}
133+
134+
/// Policy on whether to recurse into fields to retag
135+
#[derive(Copy, Clone, Debug)]
136+
pub enum RetagFields {
137+
/// Don't retag any fields.
138+
No,
139+
/// Retag all fields.
140+
Yes,
141+
/// Only retag fields of types with Scalar and ScalarPair layout,
142+
/// to match the LLVM `noalias` we generate.
143+
OnlyScalar,
144+
}
145+
146+
/// The flavor of the protector.
147+
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
148+
pub enum ProtectorKind {
149+
/// Protected against aliasing violations from other pointers.
150+
///
151+
/// Items protected like this cause UB when they are invalidated, *but* the pointer itself may
152+
/// still be used to issue a deallocation.
153+
///
154+
/// This is required for LLVM IR pointers that are `noalias` but *not* `dereferenceable`.
155+
WeakProtector,
156+
157+
/// Protected against any kind of invalidation.
158+
///
159+
/// Items protected like this cause UB when they are invalidated or the memory is deallocated.
160+
/// This is strictly stronger protection than `WeakProtector`.
161+
///
162+
/// This is required for LLVM IR pointers that are `dereferenceable` (and also allows `noalias`).
163+
StrongProtector,
164+
}
165+
166+
/// Utilities for initialization and ID generation
167+
impl GlobalStateInner {
168+
pub fn new(
169+
borrow_tracker_method: BorrowTrackerMethod,
170+
tracked_pointer_tags: FxHashSet<BorTag>,
171+
tracked_call_ids: FxHashSet<CallId>,
172+
retag_fields: RetagFields,
173+
) -> Self {
174+
GlobalStateInner {
175+
borrow_tracker_method,
176+
next_ptr_tag: BorTag::one(),
177+
base_ptr_tags: FxHashMap::default(),
178+
next_call_id: NonZeroU64::new(1).unwrap(),
179+
protected_tags: FxHashMap::default(),
180+
tracked_pointer_tags,
181+
tracked_call_ids,
182+
retag_fields,
183+
}
184+
}
185+
186+
/// Generates a new pointer tag. Remember to also check track_pointer_tags and log its creation!
187+
pub fn new_ptr(&mut self) -> BorTag {
188+
let id = self.next_ptr_tag;
189+
self.next_ptr_tag = id.succ().unwrap();
190+
id
191+
}
192+
193+
pub fn new_frame(&mut self, machine: &MiriMachine<'_, '_>) -> FrameExtra {
194+
let call_id = self.next_call_id;
195+
trace!("new_frame: Assigning call ID {}", call_id);
196+
if self.tracked_call_ids.contains(&call_id) {
197+
machine.emit_diagnostic(NonHaltingDiagnostic::CreatedCallId(call_id));
198+
}
199+
self.next_call_id = NonZeroU64::new(call_id.get() + 1).unwrap();
200+
FrameExtra { call_id, protected_tags: SmallVec::new() }
201+
}
202+
203+
pub fn end_call(&mut self, frame: &machine::FrameData<'_>) {
204+
for tag in &frame
205+
.borrow_tracker
206+
.as_ref()
207+
.expect("we should have borrow tracking data")
208+
.protected_tags
209+
{
210+
self.protected_tags.remove(tag);
211+
}
212+
}
213+
214+
pub fn base_ptr_tag(&mut self, id: AllocId, machine: &MiriMachine<'_, '_>) -> BorTag {
215+
self.base_ptr_tags.get(&id).copied().unwrap_or_else(|| {
216+
let tag = self.new_ptr();
217+
if self.tracked_pointer_tags.contains(&tag) {
218+
machine.emit_diagnostic(NonHaltingDiagnostic::CreatedPointerTag(
219+
tag.inner(),
220+
None,
221+
None,
222+
));
223+
}
224+
trace!("New allocation {:?} has base tag {:?}", id, tag);
225+
self.base_ptr_tags.try_insert(id, tag).unwrap();
226+
tag
227+
})
228+
}
229+
}
230+
231+
/// Which borrow tracking method to use
232+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
233+
pub enum BorrowTrackerMethod {
234+
/// Stacked Borrows, as implemented in borrow_tracker/stacked
235+
StackedBorrows,
236+
}
237+
238+
impl BorrowTrackerMethod {
239+
pub fn instanciate_global_state(self, config: &MiriConfig) -> GlobalState {
240+
RefCell::new(GlobalStateInner::new(
241+
self,
242+
config.tracked_pointer_tags.clone(),
243+
config.tracked_call_ids.clone(),
244+
config.retag_fields,
245+
))
246+
}
247+
}
248+
249+
impl GlobalStateInner {
250+
pub fn new_allocation(
251+
&mut self,
252+
id: AllocId,
253+
alloc_size: Size,
254+
kind: MemoryKind<machine::MiriMemoryKind>,
255+
machine: &MiriMachine<'_, '_>,
256+
) -> AllocExtra {
257+
match self.borrow_tracker_method {
258+
BorrowTrackerMethod::StackedBorrows =>
259+
AllocExtra::StackedBorrows(Box::new(RefCell::new(Stacks::new_allocation(
260+
id, alloc_size, self, kind, machine,
261+
)))),
262+
}
263+
}
264+
}
265+
266+
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
267+
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
268+
fn retag(&mut self, kind: RetagKind, place: &PlaceTy<'tcx, Provenance>) -> InterpResult<'tcx> {
269+
let this = self.eval_context_mut();
270+
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
271+
match method {
272+
BorrowTrackerMethod::StackedBorrows => this.sb_retag(kind, place),
273+
}
274+
}
275+
276+
fn retag_return_place(&mut self) -> InterpResult<'tcx> {
277+
let this = self.eval_context_mut();
278+
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
279+
match method {
280+
BorrowTrackerMethod::StackedBorrows => this.sb_retag_return_place(),
281+
}
282+
}
283+
284+
fn expose_tag(&mut self, alloc_id: AllocId, tag: BorTag) -> InterpResult<'tcx> {
285+
let this = self.eval_context_mut();
286+
let method = this.machine.borrow_tracker.as_ref().unwrap().borrow().borrow_tracker_method;
287+
match method {
288+
BorrowTrackerMethod::StackedBorrows => this.sb_expose_tag(alloc_id, tag),
289+
}
290+
}
291+
}
292+
293+
/// Extra per-allocation data for borrow tracking
294+
#[derive(Debug, Clone)]
295+
pub enum AllocExtra {
296+
/// Data corresponding to Stacked Borrows
297+
StackedBorrows(Box<RefCell<stacked_borrows::AllocExtra>>),
298+
}
299+
300+
impl AllocExtra {
301+
pub fn assert_sb(&self) -> &RefCell<stacked_borrows::AllocExtra> {
302+
match self {
303+
AllocExtra::StackedBorrows(ref sb) => sb,
304+
}
305+
}
306+
307+
pub fn assert_sb_mut(&mut self) -> &mut RefCell<stacked_borrows::AllocExtra> {
308+
match self {
309+
AllocExtra::StackedBorrows(ref mut sb) => sb,
310+
}
311+
}
312+
313+
pub fn before_memory_read<'tcx>(
314+
&self,
315+
alloc_id: AllocId,
316+
prov_extra: ProvenanceExtra,
317+
range: AllocRange,
318+
machine: &MiriMachine<'_, 'tcx>,
319+
) -> InterpResult<'tcx> {
320+
match self {
321+
AllocExtra::StackedBorrows(sb) =>
322+
sb.borrow_mut().before_memory_read(alloc_id, prov_extra, range, machine),
323+
}
324+
}
325+
326+
pub fn before_memory_write<'tcx>(
327+
&mut self,
328+
alloc_id: AllocId,
329+
prov_extra: ProvenanceExtra,
330+
range: AllocRange,
331+
machine: &mut MiriMachine<'_, 'tcx>,
332+
) -> InterpResult<'tcx> {
333+
match self {
334+
AllocExtra::StackedBorrows(sb) =>
335+
sb.get_mut().before_memory_write(alloc_id, prov_extra, range, machine),
336+
}
337+
}
338+
339+
pub fn before_memory_deallocation<'tcx>(
340+
&mut self,
341+
alloc_id: AllocId,
342+
prov_extra: ProvenanceExtra,
343+
range: AllocRange,
344+
machine: &mut MiriMachine<'_, 'tcx>,
345+
) -> InterpResult<'tcx> {
346+
match self {
347+
AllocExtra::StackedBorrows(sb) =>
348+
sb.get_mut().before_memory_deallocation(alloc_id, prov_extra, range, machine),
349+
}
350+
}
351+
352+
pub fn remove_unreachable_tags(&self, tags: &FxHashSet<BorTag>) {
353+
match self {
354+
AllocExtra::StackedBorrows(sb) => sb.borrow_mut().remove_unreachable_tags(tags),
355+
}
356+
}
357+
}
358+
359+
impl VisitTags for AllocExtra {
360+
fn visit_tags(&self, visit: &mut dyn FnMut(BorTag)) {
361+
match self {
362+
AllocExtra::StackedBorrows(sb) => sb.visit_tags(visit),
363+
}
364+
}
365+
}

0 commit comments

Comments
 (0)