@@ -16,15 +16,18 @@ use rustc_middle::ty::{
16
16
} ;
17
17
use rustc_span:: DUMMY_SP ;
18
18
use rustc_target:: abi:: Size ;
19
+ use smallvec:: SmallVec ;
19
20
use std:: collections:: HashSet ;
20
21
21
22
use crate :: * ;
22
23
23
24
pub mod diagnostics;
24
25
use diagnostics:: { AllocHistory , TagHistory } ;
25
26
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 ;
28
31
29
32
pub type CallId = NonZeroU64 ;
30
33
@@ -78,113 +81,21 @@ impl SbTagExtra {
78
81
}
79
82
}
80
83
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 ] > ,
186
98
}
187
- pub use item:: Item ;
188
99
189
100
/// Extra per-allocation state.
190
101
#[ derive( Clone , Debug ) ]
@@ -208,7 +119,11 @@ pub struct GlobalStateInner {
208
119
base_ptr_tags : FxHashMap < AllocId , SbTag > ,
209
120
/// Next unused call ID (for protectors).
210
121
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.
212
127
protected_tags : FxHashSet < SbTag > ,
213
128
/// The pointer ids to trace
214
129
tracked_pointer_tags : HashSet < SbTag > ,
@@ -287,18 +202,23 @@ impl GlobalStateInner {
287
202
id
288
203
}
289
204
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 ) ) ;
295
210
}
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 ( ) }
298
213
}
299
214
300
215
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
+ {
302
222
self . protected_tags . remove ( tag) ;
303
223
}
304
224
}
@@ -407,17 +327,40 @@ impl<'tcx> Stack {
407
327
return Ok ( ( ) ) ;
408
328
}
409
329
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).
410
343
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.
411
347
let call_id = threads
412
348
. all_stacks ( )
413
349
. 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 )
416
359
. unwrap ( ) ; // FIXME: Surely we should find something, but a panic seems wrong here?
417
360
if let Some ( ( tag, _alloc_range, _offset, _access) ) = provoking_access {
418
361
Err ( err_sb_ub (
419
362
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 {:?}" ,
421
364
tag, item, call_id
422
365
) ,
423
366
None ,
@@ -426,7 +369,7 @@ impl<'tcx> Stack {
426
369
} else {
427
370
Err ( err_sb_ub (
428
371
format ! (
429
- "deallocating while item is protected: {:?} ( call {:?}) " ,
372
+ "deallocating while item {:?} is protected by call {:?}" ,
430
373
item, call_id
431
374
) ,
432
375
None ,
@@ -904,7 +847,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
904
847
) ;
905
848
906
849
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) ;
908
851
this. machine . stacked_borrows . as_mut ( ) . unwrap ( ) . get_mut ( ) . protected_tags . insert ( new_tag) ;
909
852
}
910
853
// FIXME: can't hold the current span handle across the borrows of self above
0 commit comments