Skip to content

Commit 481ce3e

Browse files
committed
Store protectors outside Item, pack Tag and Perm
Previously, Item was a struct of a NonZeroU64, an Option which was usually unset or irrelevant, and a 4-variant enum. So collectively, the size of an Item was 24 bytes, but only 8 bytes were used for the most part. So this takes advantage of the fact that it is probably impossible to exhaust the total space of SbTags, and steals 3 bits from it to pack the whole struct into a single u64. This bit-packing means that we reduce peak memory usage when Miri goes memory-bound by ~3x. We also get CPU performance improvements of varying size, because not only are we simply accessing less memory, we can now compare a Vec<Item> using a memcmp because it does not have any padding.
1 parent 1118d94 commit 481ce3e

13 files changed

+202
-106
lines changed

src/machine.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,18 @@ pub struct FrameData<'tcx> {
5454
/// for the start of this frame. When we finish executing this frame,
5555
/// we use this to register a completed event with `measureme`.
5656
pub timing: Option<measureme::DetachedTiming>,
57+
58+
pub protected_tags: Vec<SbTag>,
5759
}
5860

5961
impl<'tcx> std::fmt::Debug for FrameData<'tcx> {
6062
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6163
// Omitting `timing`, it does not support `Debug`.
62-
let FrameData { call_id, catch_unwind, timing: _ } = self;
64+
let FrameData { call_id, catch_unwind, timing: _, protected_tags } = self;
6365
f.debug_struct("FrameData")
6466
.field("call_id", call_id)
6567
.field("catch_unwind", catch_unwind)
68+
.field("protected_tags", protected_tags)
6669
.finish()
6770
}
6871
}
@@ -892,7 +895,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
892895
stacked_borrows.borrow_mut().new_call()
893896
});
894897

895-
let extra = FrameData { call_id, catch_unwind: None, timing };
898+
let extra = FrameData { call_id, catch_unwind: None, timing, protected_tags: Vec::new() };
896899
Ok(frame.with_extra(extra))
897900
}
898901

@@ -936,7 +939,11 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'mir, 'tcx> {
936939
) -> InterpResult<'tcx, StackPopJump> {
937940
let timing = frame.extra.timing.take();
938941
if let Some(stacked_borrows) = &ecx.machine.stacked_borrows {
939-
stacked_borrows.borrow_mut().end_call(frame.extra.call_id);
942+
let mut sb = stacked_borrows.borrow_mut();
943+
sb.end_call(frame.extra.call_id);
944+
for tag in frame.extra.protected_tags.drain(..) {
945+
sb.protected_tags.remove(&tag);
946+
}
940947
}
941948
let res = ecx.handle_stack_pop_unwind(frame.extra, unwinding);
942949
if let Some(profiler) = ecx.machine.profiler.as_ref() {

src/stacked_borrows.rs

Lines changed: 136 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -92,27 +92,90 @@ pub enum Permission {
9292
Disabled,
9393
}
9494

95-
/// An item in the per-location borrow stack.
96-
#[derive(Copy, Clone, Hash, PartialEq, Eq)]
97-
pub struct Item {
98-
/// The permission this item grants.
99-
perm: Permission,
100-
/// The pointers the permission is granted to.
101-
tag: SbTag,
102-
/// An optional protector, ensuring the item cannot get popped until `CallId` is over.
103-
protector: Option<CallId>,
95+
impl Permission {
96+
fn to_bits(self) -> u64 {
97+
match self {
98+
Permission::Unique => 0,
99+
Permission::SharedReadWrite => 1,
100+
Permission::SharedReadOnly => 2,
101+
Permission::Disabled => 3,
102+
}
103+
}
104+
105+
fn from_bits(perm: u64) -> Self {
106+
match perm {
107+
0 => Permission::Unique,
108+
1 => Permission::SharedReadWrite,
109+
2 => Permission::SharedReadOnly,
110+
3 => Permission::Disabled,
111+
_ => unreachable!(),
112+
}
113+
}
104114
}
105115

106-
impl fmt::Debug for Item {
107-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108-
write!(f, "[{:?} for {:?}", self.perm, self.tag)?;
109-
if let Some(call) = self.protector {
110-
write!(f, " (call {})", call)?;
116+
mod item {
117+
use super::{Permission, SbTag};
118+
use std::fmt;
119+
120+
/// An item in the per-location borrow stack.
121+
#[derive(Copy, Clone, Hash, PartialEq, Eq)]
122+
pub struct Item(u64);
123+
124+
// An Item contains 3 bitfields:
125+
// * Bits 0-61 store an SbTag
126+
// * Bits 61-63 store a Permission
127+
// * Bit 64 stores a flag which indicates if we have a protector
128+
const TAG_MASK: u64 = u64::MAX >> 3;
129+
const PERM_MASK: u64 = 0x3 << 61;
130+
const PROTECTED_MASK: u64 = 0x1 << 63;
131+
132+
const PERM_SHIFT: u64 = 61;
133+
const PROTECTED_SHIFT: u64 = 63;
134+
135+
impl Item {
136+
pub fn new(tag: SbTag, perm: Permission, protected: bool) -> Self {
137+
assert!(tag.0.get() <= TAG_MASK);
138+
let packed_tag = tag.0.get();
139+
let packed_perm = perm.to_bits() << PERM_SHIFT;
140+
let packed_protected = (protected as u64) << PROTECTED_SHIFT;
141+
142+
let new = Self(packed_tag + packed_perm + packed_protected);
143+
144+
debug_assert!(new.tag() == tag);
145+
debug_assert!(new.perm() == perm);
146+
debug_assert!(new.protected() == protected);
147+
148+
new
149+
}
150+
151+
/// The pointers the permission is granted to.
152+
pub fn tag(self) -> SbTag {
153+
unsafe { SbTag(std::num::NonZeroU64::new_unchecked(self.0 & TAG_MASK)) }
154+
}
155+
156+
/// The permission this item grants.
157+
pub fn perm(self) -> Permission {
158+
Permission::from_bits((self.0 & PERM_MASK) >> PERM_SHIFT)
159+
}
160+
161+
/// Whether or not there is a protector for this tag
162+
pub fn protected(self) -> bool {
163+
self.0 & PROTECTED_MASK > 0
164+
}
165+
166+
/// Set the Permission stored in this Item to Permission::Disabled
167+
pub fn set_disabled(&mut self) {
168+
self.0 |= Permission::Disabled.to_bits() << PERM_SHIFT;
169+
}
170+
}
171+
172+
impl fmt::Debug for Item {
173+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174+
write!(f, "[{:?} for {:?}]", self.perm(), self.tag())
111175
}
112-
write!(f, "]")?;
113-
Ok(())
114176
}
115177
}
178+
pub use item::Item;
116179

117180
/// Extra per-allocation state.
118181
#[derive(Clone, Debug)]
@@ -138,6 +201,8 @@ pub struct GlobalStateInner {
138201
next_call_id: CallId,
139202
/// Those call IDs corresponding to functions that are still running.
140203
active_calls: FxHashSet<CallId>,
204+
/// All tags currently protected
205+
pub(crate) protected_tags: FxHashMap<SbTag, CallId>,
141206
/// The pointer ids to trace
142207
tracked_pointer_tags: HashSet<SbTag>,
143208
/// The call ids to trace
@@ -202,6 +267,7 @@ impl GlobalStateInner {
202267
base_ptr_tags: FxHashMap::default(),
203268
next_call_id: NonZeroU64::new(1).unwrap(),
204269
active_calls: FxHashSet::default(),
270+
protected_tags: FxHashMap::default(),
205271
tracked_pointer_tags,
206272
tracked_call_ids,
207273
retag_fields,
@@ -230,10 +296,6 @@ impl GlobalStateInner {
230296
assert!(self.active_calls.remove(&id));
231297
}
232298

233-
fn is_active(&self, id: CallId) -> bool {
234-
self.active_calls.contains(&id)
235-
}
236-
237299
pub fn base_ptr_tag(&mut self, id: AllocId) -> SbTag {
238300
self.base_ptr_tags.get(&id).copied().unwrap_or_else(|| {
239301
let tag = self.new_ptr();
@@ -287,7 +349,7 @@ impl<'tcx> Stack {
287349
/// Find the first write-incompatible item above the given one --
288350
/// i.e, find the height to which the stack will be truncated when writing to `granting`.
289351
fn find_first_write_incompatible(&self, granting: usize) -> usize {
290-
let perm = self.get(granting).unwrap().perm;
352+
let perm = self.get(granting).unwrap().perm();
291353
match perm {
292354
Permission::SharedReadOnly => bug!("Cannot use SharedReadOnly for writing"),
293355
Permission::Disabled => bug!("Cannot use Disabled for anything"),
@@ -299,7 +361,7 @@ impl<'tcx> Stack {
299361
// The SharedReadWrite *just* above us are compatible, to skip those.
300362
let mut idx = granting + 1;
301363
while let Some(item) = self.get(idx) {
302-
if item.perm == Permission::SharedReadWrite {
364+
if item.perm() == Permission::SharedReadWrite {
303365
// Go on.
304366
idx += 1;
305367
} else {
@@ -326,31 +388,36 @@ impl<'tcx> Stack {
326388
global: &GlobalStateInner,
327389
alloc_history: &mut AllocHistory,
328390
) -> InterpResult<'tcx> {
329-
if global.tracked_pointer_tags.contains(&item.tag) {
391+
if global.tracked_pointer_tags.contains(&item.tag()) {
330392
register_diagnostic(NonHaltingDiagnostic::PoppedPointerTag(
331393
*item,
332394
provoking_access.map(|(tag, _alloc_range, _size, access)| (tag, access)),
333395
));
334396
}
335397

336-
if let Some(call) = item.protector {
337-
if global.is_active(call) {
338-
if let Some((tag, _alloc_range, _offset, _access)) = provoking_access {
339-
Err(err_sb_ub(
340-
format!(
341-
"not granting access to tag {:?} because incompatible item is protected: {:?}",
342-
tag, item
343-
),
344-
None,
345-
tag.and_then(|tag| alloc_history.get_logs_relevant_to(tag, Some(item.tag))),
346-
))?
347-
} else {
348-
Err(err_sb_ub(
349-
format!("deallocating while item is protected: {:?}", item),
350-
None,
351-
None,
352-
))?
353-
}
398+
if !item.protected() {
399+
return Ok(());
400+
}
401+
402+
if let Some(call_id) = global.protected_tags.get(&item.tag()) {
403+
if let Some((tag, _alloc_range, _offset, _access)) = provoking_access {
404+
Err(err_sb_ub(
405+
format!(
406+
"not granting access to tag {:?} because incompatible item is protected: {:?} (call {:?})",
407+
tag, item, call_id
408+
),
409+
None,
410+
tag.and_then(|tag| alloc_history.get_logs_relevant_to(tag, Some(item.tag()))),
411+
))?
412+
} else {
413+
Err(err_sb_ub(
414+
format!(
415+
"deallocating while item is protected: {:?} (call {:?})",
416+
item, call_id
417+
),
418+
None,
419+
None,
420+
))?
354421
}
355422
}
356423
Ok(())
@@ -400,7 +467,7 @@ impl<'tcx> Stack {
400467
global,
401468
alloc_history,
402469
)?;
403-
alloc_history.log_invalidation(item.tag, alloc_range, current_span);
470+
alloc_history.log_invalidation(item.tag(), alloc_range, current_span);
404471
Ok(())
405472
})?;
406473
} else {
@@ -426,7 +493,7 @@ impl<'tcx> Stack {
426493
global,
427494
alloc_history,
428495
)?;
429-
alloc_history.log_invalidation(item.tag, alloc_range, current_span);
496+
alloc_history.log_invalidation(item.tag(), alloc_range, current_span);
430497
Ok(())
431498
})?;
432499
}
@@ -439,9 +506,9 @@ impl<'tcx> Stack {
439506
for i in 0..self.len() {
440507
let item = self.get(i).unwrap();
441508
// Skip disabled items, they cannot be matched anyway.
442-
if !matches!(item.perm, Permission::Disabled) {
509+
if !matches!(item.perm(), Permission::Disabled) {
443510
// We are looking for a strict upper bound, so add 1 to this tag.
444-
max = cmp::max(item.tag.0.checked_add(1).unwrap(), max);
511+
max = cmp::max(item.tag().0.checked_add(1).unwrap(), max);
445512
}
446513
}
447514
if let Some(unk) = self.unknown_bottom() {
@@ -505,7 +572,7 @@ impl<'tcx> Stack {
505572
) -> InterpResult<'tcx> {
506573
// Figure out which access `perm` corresponds to.
507574
let access =
508-
if new.perm.grants(AccessKind::Write) { AccessKind::Write } else { AccessKind::Read };
575+
if new.perm().grants(AccessKind::Write) { AccessKind::Write } else { AccessKind::Read };
509576

510577
// Now we figure out which item grants our parent (`derived_from`) this kind of access.
511578
// We use that to determine where to put the new item.
@@ -517,7 +584,7 @@ impl<'tcx> Stack {
517584
// Compute where to put the new item.
518585
// Either way, we ensure that we insert the new item in a way such that between
519586
// `derived_from` and the new one, there are only items *compatible with* `derived_from`.
520-
let new_idx = if new.perm == Permission::SharedReadWrite {
587+
let new_idx = if new.perm() == Permission::SharedReadWrite {
521588
assert!(
522589
access == AccessKind::Write,
523590
"this case only makes sense for stack-like accesses"
@@ -578,7 +645,7 @@ impl<'tcx> Stack {
578645
impl<'tcx> Stacks {
579646
/// Creates new stack with initial tag.
580647
fn new(size: Size, perm: Permission, tag: SbTag) -> Self {
581-
let item = Item { perm, tag, protector: None };
648+
let item = Item::new(tag, perm, false);
582649
let stack = Stack::new(item);
583650

584651
Stacks {
@@ -808,7 +875,6 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
808875
});
809876
}
810877

811-
let protector = if protect { Some(this.frame().extra.call_id) } else { None };
812878
trace!(
813879
"reborrow: {} reference {:?} derived from {:?} (pointee {}): {:?}, size {}",
814880
kind,
@@ -819,6 +885,22 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
819885
size.bytes()
820886
);
821887

888+
// FIXME: This just protects everything, which is wrong. At the very least, we should not
889+
// protect anything that contains an UnsafeCell.
890+
if protect {
891+
this.frame_mut().extra.protected_tags.push(new_tag);
892+
let call_id = this.frame().extra.call_id;
893+
this.machine
894+
.stacked_borrows
895+
.as_mut()
896+
.unwrap()
897+
.get_mut()
898+
.protected_tags
899+
.insert(new_tag, call_id);
900+
}
901+
// FIXME: can't hold the current span handle across the borrows of self above
902+
let current_span = &mut this.machine.current_span();
903+
822904
// Update the stacks.
823905
// Make sure that raw pointers and mutable shared references are reborrowed "weak":
824906
// There could be existing unique pointers reborrowed from them that should remain valid!
@@ -855,14 +937,14 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
855937
} else {
856938
Permission::SharedReadWrite
857939
};
858-
let protector = if frozen {
859-
protector
940+
let protected = if frozen {
941+
protect
860942
} else {
861943
// We do not protect inside UnsafeCell.
862944
// This fixes https://github.com/rust-lang/rust/issues/55005.
863-
None
945+
false
864946
};
865-
let item = Item { perm, tag: new_tag, protector };
947+
let item = Item::new(new_tag, perm, protected);
866948
let mut global = this.machine.stacked_borrows.as_ref().unwrap().borrow_mut();
867949
stacked_borrows.for_each(range, |offset, stack, history, exposed_tags| {
868950
stack.grant(
@@ -888,7 +970,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
888970
.as_mut()
889971
.expect("we should have Stacked Borrows data")
890972
.borrow_mut();
891-
let item = Item { perm, tag: new_tag, protector };
973+
let item = Item::new(new_tag, perm, protect);
892974
let range = alloc_range(base_offset, size);
893975
let mut global = machine.stacked_borrows.as_ref().unwrap().borrow_mut();
894976
let current_span = &mut machine.current_span(); // `get_alloc_extra_mut` invalidated our old `current_span`

src/stacked_borrows/diagnostics.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ impl AllocHistory {
141141
) -> InterpError<'tcx> {
142142
let action = format!(
143143
"trying to reborrow {derived_from:?} for {:?} permission at {alloc_id:?}[{:#x}]",
144-
new.perm,
144+
new.perm(),
145145
error_offset.bytes(),
146146
);
147147
err_sb_ub(
@@ -185,7 +185,7 @@ fn error_cause(stack: &Stack, tag: SbTagExtra) -> &'static str {
185185
if let SbTagExtra::Concrete(tag) = tag {
186186
if (0..stack.len())
187187
.map(|i| stack.get(i).unwrap())
188-
.any(|item| item.tag == tag && item.perm != Permission::Disabled)
188+
.any(|item| item.tag() == tag && item.perm() != Permission::Disabled)
189189
{
190190
", but that tag only grants SharedReadOnly permission for this location"
191191
} else {

0 commit comments

Comments
 (0)