Skip to content

Commit 4eff60a

Browse files
committed
Rearrange and document the new implementation
stacked_borrow now has an item module, and its own FrameExtra. These serve to protect the implementation of Item (which is a bunch of bit-packing tricks) from the primary logic of Stacked Borrows, and the FrameExtra we have separates Stacked Borrows more cleanly from the interpreter itself. The new strategy for checking protectors also makes some subtle performance tradeoffs, so they are now documented in Stack::item_popped because that function primarily benefits from them, and it also touches every aspect of them. Also separating the actual CallId that is protecting a Tag from the Tag makes it inconvienent to reproduce exactly the same protector errors, so this also takes the opportunity to use some slightly cleaner English in those errors. We need to make some change, might as well make it good.
1 parent afa1ddd commit 4eff60a

18 files changed

+206
-161
lines changed

src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ pub use crate::mono_hash_map::MonoHashMap;
9090
pub use crate::operator::EvalContextExt as OperatorEvalContextExt;
9191
pub use crate::range_map::RangeMap;
9292
pub use crate::stacked_borrows::{
93-
stack::Stack, CallId, EvalContextExt as StackedBorEvalContextExt, Item, Permission, SbTag,
94-
SbTagExtra, Stacks,
93+
CallId, EvalContextExt as StackedBorEvalContextExt, Item, Permission, SbTag, SbTagExtra, Stack,
94+
Stacks,
9595
};
9696
pub use crate::sync::{CondvarId, EvalContextExt as SyncEvalContextExt, MutexId, RwLockId};
9797
pub use crate::thread::{

src/machine.rs

+8-11
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use std::borrow::Cow;
55
use std::cell::RefCell;
66
use std::collections::HashSet;
77
use std::fmt;
8-
use std::num::NonZeroU64;
98
use std::time::Instant;
109

1110
use rand::rngs::StdRng;
@@ -43,7 +42,7 @@ pub const NUM_CPUS: u64 = 1;
4342
/// Extra data stored with each stack frame
4443
pub struct FrameData<'tcx> {
4544
/// Extra data for Stacked Borrows.
46-
pub call_id: stacked_borrows::CallId,
45+
pub stacked_borrows: Option<stacked_borrows::FrameExtra>,
4746

4847
/// If this is Some(), then this is a special "catch unwind" frame (the frame of `try_fn`
4948
/// called by `try`). When this frame is popped during unwinding a panic,
@@ -54,18 +53,15 @@ pub struct FrameData<'tcx> {
5453
/// for the start of this frame. When we finish executing this frame,
5554
/// we use this to register a completed event with `measureme`.
5655
pub timing: Option<measureme::DetachedTiming>,
57-
58-
pub protected_tags: Vec<SbTag>,
5956
}
6057

6158
impl<'tcx> std::fmt::Debug for FrameData<'tcx> {
6259
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6360
// Omitting `timing`, it does not support `Debug`.
64-
let FrameData { call_id, catch_unwind, timing: _, protected_tags } = self;
61+
let FrameData { stacked_borrows, catch_unwind, timing: _ } = self;
6562
f.debug_struct("FrameData")
66-
.field("call_id", call_id)
63+
.field("stacked_borrows", stacked_borrows)
6764
.field("catch_unwind", catch_unwind)
68-
.field("protected_tags", protected_tags)
6965
.finish()
7066
}
7167
}
@@ -894,11 +890,12 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
894890
};
895891

896892
let stacked_borrows = ecx.machine.stacked_borrows.as_ref();
897-
let call_id = stacked_borrows.map_or(NonZeroU64::new(1).unwrap(), |stacked_borrows| {
898-
stacked_borrows.borrow_mut().new_call()
899-
});
900893

901-
let extra = FrameData { call_id, catch_unwind: None, timing, protected_tags: Vec::new() };
894+
let extra = FrameData {
895+
stacked_borrows: stacked_borrows.map(|sb| sb.borrow_mut().new_frame()),
896+
catch_unwind: None,
897+
timing,
898+
};
902899
Ok(frame.with_extra(extra))
903900
}
904901

src/stacked_borrows.rs

+65-122
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,18 @@ use rustc_middle::ty::{
1616
};
1717
use rustc_span::DUMMY_SP;
1818
use rustc_target::abi::Size;
19+
use smallvec::SmallVec;
1920
use std::collections::HashSet;
2021

2122
use crate::*;
2223

2324
pub mod diagnostics;
2425
use diagnostics::{AllocHistory, TagHistory};
2526

26-
pub mod stack;
27-
use stack::Stack;
27+
mod item;
28+
pub use item::{Item, Permission};
29+
mod stack;
30+
pub use stack::Stack;
2831

2932
pub type CallId = NonZeroU64;
3033

@@ -78,113 +81,21 @@ impl SbTagExtra {
7881
}
7982
}
8083

81-
/// Indicates which permission is granted (by this item to some pointers)
82-
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
83-
pub enum Permission {
84-
/// Grants unique mutable access.
85-
Unique,
86-
/// Grants shared mutable access.
87-
SharedReadWrite,
88-
/// Grants shared read-only access.
89-
SharedReadOnly,
90-
/// Grants no access, but separates two groups of SharedReadWrite so they are not
91-
/// all considered mutually compatible.
92-
Disabled,
93-
}
94-
95-
impl Permission {
96-
const UNIQUE: u64 = 0;
97-
const SHARED_READ_WRITE: u64 = 1;
98-
const SHARED_READ_ONLY: u64 = 2;
99-
const DISABLED: u64 = 3;
100-
101-
fn to_bits(self) -> u64 {
102-
match self {
103-
Permission::Unique => Self::UNIQUE,
104-
Permission::SharedReadWrite => Self::SHARED_READ_WRITE,
105-
Permission::SharedReadOnly => Self::SHARED_READ_ONLY,
106-
Permission::Disabled => Self::DISABLED,
107-
}
108-
}
109-
110-
fn from_bits(perm: u64) -> Self {
111-
match perm {
112-
Self::UNIQUE => Permission::Unique,
113-
Self::SHARED_READ_WRITE => Permission::SharedReadWrite,
114-
Self::SHARED_READ_ONLY => Permission::SharedReadOnly,
115-
Self::DISABLED => Permission::Disabled,
116-
_ => unreachable!(),
117-
}
118-
}
119-
}
120-
121-
mod item {
122-
use super::{Permission, SbTag};
123-
use std::fmt;
124-
use std::num::NonZeroU64;
125-
126-
/// An item in the per-location borrow stack.
127-
#[derive(Copy, Clone, Hash, PartialEq, Eq)]
128-
pub struct Item(u64);
129-
130-
// An Item contains 3 bitfields:
131-
// * Bits 0-61 store an SbTag
132-
// * Bits 61-63 store a Permission
133-
// * Bit 64 stores a flag which indicates if we have a protector
134-
const TAG_MASK: u64 = u64::MAX >> 3;
135-
const PERM_MASK: u64 = 0x3 << 61;
136-
const PROTECTED_MASK: u64 = 0x1 << 63;
137-
138-
const PERM_SHIFT: u64 = 61;
139-
const PROTECTED_SHIFT: u64 = 63;
140-
141-
impl Item {
142-
pub fn new(tag: SbTag, perm: Permission, protected: bool) -> Self {
143-
assert!(tag.0.get() <= TAG_MASK);
144-
let packed_tag = tag.0.get();
145-
let packed_perm = perm.to_bits() << PERM_SHIFT;
146-
let packed_protected = (protected as u64) << PROTECTED_SHIFT;
147-
148-
let new = Self(packed_tag | packed_perm | packed_protected);
149-
150-
debug_assert!(new.tag() == tag);
151-
debug_assert!(new.perm() == perm);
152-
debug_assert!(new.protected() == protected);
153-
154-
new
155-
}
156-
157-
/// The pointers the permission is granted to.
158-
pub fn tag(self) -> SbTag {
159-
SbTag(NonZeroU64::new(self.0 & TAG_MASK).unwrap())
160-
}
161-
162-
/// The permission this item grants.
163-
pub fn perm(self) -> Permission {
164-
Permission::from_bits((self.0 & PERM_MASK) >> PERM_SHIFT)
165-
}
166-
167-
/// Whether or not there is a protector for this tag
168-
pub fn protected(self) -> bool {
169-
self.0 & PROTECTED_MASK > 0
170-
}
171-
172-
/// Set the Permission stored in this Item to Permission::Disabled
173-
pub fn set_disabled(&mut self) {
174-
// Clear the current set permission
175-
self.0 &= !PERM_MASK;
176-
// Write Permission::Disabled to the Permission bits
177-
self.0 |= Permission::Disabled.to_bits() << PERM_SHIFT;
178-
}
179-
}
180-
181-
impl fmt::Debug for Item {
182-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183-
write!(f, "[{:?} for {:?}]", self.perm(), self.tag())
184-
}
185-
}
84+
#[derive(Debug)]
85+
pub struct FrameExtra {
86+
/// The ID of the call this frame corresponds to.
87+
call_id: CallId,
88+
89+
/// If this frame is protecting any tags, they are listed here. We use this list to do
90+
/// incremental updates of the global list of protected tags stored in the
91+
/// `stacked_borrows::GlobalState` upon function return, and if we attempt to pop a protected
92+
/// tag, to identify which call is responsible for protecting the tag.
93+
/// See `Stack::item_popped` for more explanation.
94+
///
95+
/// This will contain one tag per reference passed to the function, so
96+
/// a size of 2 is enough for the vast majority of functions.
97+
protected_tags: SmallVec<[SbTag; 2]>,
18698
}
187-
pub use item::Item;
18899

189100
/// Extra per-allocation state.
190101
#[derive(Clone, Debug)]
@@ -208,7 +119,11 @@ pub struct GlobalStateInner {
208119
base_ptr_tags: FxHashMap<AllocId, SbTag>,
209120
/// Next unused call ID (for protectors).
210121
next_call_id: CallId,
211-
/// All tags currently protected
122+
/// All currently protected tags.
123+
/// An item is protected if its tag is in this set, *and* it has the "protected" bit set.
124+
/// We add tags to this when they are created with a protector in `reborrow`, and
125+
/// we remove tags from this when the call which is protecting them returns, in
126+
/// `GlobalStateInner::end_call`. See `Stack::item_popped` for more details.
212127
protected_tags: FxHashSet<SbTag>,
213128
/// The pointer ids to trace
214129
tracked_pointer_tags: HashSet<SbTag>,
@@ -287,18 +202,23 @@ impl GlobalStateInner {
287202
id
288203
}
289204

290-
pub fn new_call(&mut self) -> CallId {
291-
let id = self.next_call_id;
292-
trace!("new_call: Assigning ID {}", id);
293-
if self.tracked_call_ids.contains(&id) {
294-
register_diagnostic(NonHaltingDiagnostic::CreatedCallId(id));
205+
pub fn new_frame(&mut self) -> FrameExtra {
206+
let call_id = self.next_call_id;
207+
trace!("new_frame: Assigning call ID {}", call_id);
208+
if self.tracked_call_ids.contains(&call_id) {
209+
register_diagnostic(NonHaltingDiagnostic::CreatedCallId(call_id));
295210
}
296-
self.next_call_id = NonZeroU64::new(id.get() + 1).unwrap();
297-
id
211+
self.next_call_id = NonZeroU64::new(call_id.get() + 1).unwrap();
212+
FrameExtra { call_id, protected_tags: SmallVec::new() }
298213
}
299214

300215
pub fn end_call(&mut self, frame: &machine::FrameData<'_>) {
301-
for tag in &frame.protected_tags {
216+
for tag in &frame
217+
.stacked_borrows
218+
.as_ref()
219+
.expect("we should have Stacked Borrows data")
220+
.protected_tags
221+
{
302222
self.protected_tags.remove(tag);
303223
}
304224
}
@@ -407,17 +327,40 @@ impl<'tcx> Stack {
407327
return Ok(());
408328
}
409329

330+
// We store tags twice, once in global.protected_tags and once in each call frame.
331+
// We do this because consulting a single global set in this function is faster
332+
// than attempting to search all call frames in the program for the `FrameExtra`
333+
// (if any) which is protecting the popped tag.
334+
//
335+
// This duplication trades off making `end_call` slower to make this function faster. This
336+
// trade-off is profitable in practice for a combination of two reasons.
337+
// 1. A single protected tag can (and does in some programs) protect thousands of `Item`s.
338+
// Therefore, adding overhead to in function call/return is profitable even if it only
339+
// saves a little work in this function.
340+
// 2. Most frames protect only one or two tags. So this duplicative global turns a search
341+
// which ends up about linear in the number of protected tags in the program into a
342+
// constant time check (and a slow linear, because the tags in the frames aren't contiguous).
410343
if global.protected_tags.contains(&item.tag()) {
344+
// This path is cold because it is fatal to the program. So here it is fine to do the
345+
// more expensive search to figure out which call is responsible for protecting this
346+
// tag.
411347
let call_id = threads
412348
.all_stacks()
413349
.flatten()
414-
.find(|t| t.extra.protected_tags.contains(&item.tag()))
415-
.map(|frame| frame.extra.call_id)
350+
.map(|frame| {
351+
frame
352+
.extra
353+
.stacked_borrows
354+
.as_ref()
355+
.expect("we should have Stacked Borrows data")
356+
})
357+
.find(|frame| frame.protected_tags.contains(&item.tag()))
358+
.map(|frame| frame.call_id)
416359
.unwrap(); // FIXME: Surely we should find something, but a panic seems wrong here?
417360
if let Some((tag, _alloc_range, _offset, _access)) = provoking_access {
418361
Err(err_sb_ub(
419362
format!(
420-
"not granting access to tag {:?} because incompatible item is protected: {:?} (call {:?})",
363+
"not granting access to tag {:?} because incompatible item {:?} is protected by call {:?}",
421364
tag, item, call_id
422365
),
423366
None,
@@ -426,7 +369,7 @@ impl<'tcx> Stack {
426369
} else {
427370
Err(err_sb_ub(
428371
format!(
429-
"deallocating while item is protected: {:?} (call {:?})",
372+
"deallocating while item {:?} is protected by call {:?}",
430373
item, call_id
431374
),
432375
None,
@@ -904,7 +847,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
904847
);
905848

906849
if protect {
907-
this.frame_mut().extra.protected_tags.push(new_tag);
850+
this.frame_mut().extra.stacked_borrows.as_mut().unwrap().protected_tags.push(new_tag);
908851
this.machine.stacked_borrows.as_mut().unwrap().get_mut().protected_tags.insert(new_tag);
909852
}
910853
// FIXME: can't hold the current span handle across the borrows of self above

0 commit comments

Comments
 (0)