@@ -27,8 +27,7 @@ pub struct Stack {
27
27
/// we never have the unknown-to-known boundary in an SRW group.
28
28
unknown_bottom : Option < SbTag > ,
29
29
30
- /// A small LRU cache of searches of the borrow stack. This only caches accesses of `Tagged`,
31
- /// because those are unique in the stack.
30
+ /// A small LRU cache of searches of the borrow stack.
32
31
#[ cfg( feature = "stack-cache" ) ]
33
32
cache : StackCache ,
34
33
/// On a read, we need to disable all `Unique` above the granting item. We can avoid most of
@@ -42,6 +41,11 @@ pub struct Stack {
42
41
/// probably-cold random access into the borrow stack to figure out what `Permission` an
43
42
/// `SbTag` grants. We could avoid this by also storing the `Permission` in the cache, but
44
43
/// most lookups into the cache are immediately followed by access of the full borrow stack anyway.
44
+ ///
45
+ /// It may seem like maintaining this cache is a waste for small stacks, but
46
+ /// (a) iterating over small fixed-size arrays is super fast, and (b) empirically this helps *a lot*,
47
+ /// probably because runtime is dominated by large stacks.
48
+ /// arrays is super fast
45
49
#[ cfg( feature = "stack-cache" ) ]
46
50
#[ derive( Clone , Debug ) ]
47
51
struct StackCache {
@@ -81,7 +85,9 @@ impl<'tcx> Stack {
81
85
/// - There are no Unique tags outside of first_unique..last_unique
82
86
#[ cfg( feature = "expensive-debug-assertions" ) ]
83
87
fn verify_cache_consistency ( & self ) {
84
- if self . borrows . len ( ) > CACHE_LEN {
88
+ // Only a full cache needs to be valid. Also see the comments in find_granting_cache
89
+ // and set_unknown_bottom.
90
+ if self . borrows . len ( ) >= CACHE_LEN {
85
91
for ( tag, stack_idx) in self . cache . tags . iter ( ) . zip ( self . cache . idx . iter ( ) ) {
86
92
assert_eq ! ( self . borrows[ * stack_idx] . tag, * tag) ;
87
93
}
@@ -154,7 +160,7 @@ impl<'tcx> Stack {
154
160
}
155
161
156
162
// If we didn't find the tag in the cache, fall back to a linear search of the
157
- // whole stack, and add the tag to the stack .
163
+ // whole stack, and add the tag to the cache .
158
164
for ( stack_idx, item) in self . borrows . iter ( ) . enumerate ( ) . rev ( ) {
159
165
if tag == item. tag && item. perm . grants ( access) {
160
166
#[ cfg( feature = "stack-cache" ) ]
@@ -185,8 +191,11 @@ impl<'tcx> Stack {
185
191
// If we found the tag, look up its position in the stack to see if it grants
186
192
// the required permission
187
193
if self . borrows [ stack_idx] . perm . grants ( access) {
188
- // If it does, and it's not already in the most-recently-used position, move it there.
189
- // Except if the tag is in position 1, this is equivalent to just a swap, so do that.
194
+ // If it does, and it's not already in the most-recently-used position, re-insert it at
195
+ // the most-recently-used position. This technically reduces the efficiency of the
196
+ // cache by duplicating elements, but current benchmarks do not seem to benefit from
197
+ // avoiding this duplication.
198
+ // But if the tag is in position 1, avoiding the duplicating add is trivial.
190
199
if cache_idx == 1 {
191
200
self . cache . tags . swap ( 0 , 1 ) ;
192
201
self . cache . idx . swap ( 0 , 1 ) ;
@@ -287,7 +296,6 @@ impl<'tcx> Stack {
287
296
let unique_range = 0 ..self . len ( ) ;
288
297
289
298
if disable_start <= unique_range. end {
290
- // add 1 so we don't disable the granting item
291
299
let lower = unique_range. start . max ( disable_start) ;
292
300
let upper = ( unique_range. end + 1 ) . min ( self . borrows . len ( ) ) ;
293
301
for item in & mut self . borrows [ lower..upper] {
0 commit comments