@@ -27,8 +27,7 @@ pub struct Stack {
2727 /// we never have the unknown-to-known boundary in an SRW group.
2828 unknown_bottom : Option < SbTag > ,
2929
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.
3231 #[ cfg( feature = "stack-cache" ) ]
3332 cache : StackCache ,
3433 /// 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 {
4241/// probably-cold random access into the borrow stack to figure out what `Permission` an
4342/// `SbTag` grants. We could avoid this by also storing the `Permission` in the cache, but
4443/// 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
4549#[ cfg( feature = "stack-cache" ) ]
4650#[ derive( Clone , Debug ) ]
4751struct StackCache {
@@ -81,7 +85,9 @@ impl<'tcx> Stack {
8185 /// - There are no Unique tags outside of first_unique..last_unique
8286 #[ cfg( feature = "expensive-debug-assertions" ) ]
8387 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 {
8591 for ( tag, stack_idx) in self . cache . tags . iter ( ) . zip ( self . cache . idx . iter ( ) ) {
8692 assert_eq ! ( self . borrows[ * stack_idx] . tag, * tag) ;
8793 }
@@ -154,7 +160,7 @@ impl<'tcx> Stack {
154160 }
155161
156162 // 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 .
158164 for ( stack_idx, item) in self . borrows . iter ( ) . enumerate ( ) . rev ( ) {
159165 if tag == item. tag && item. perm . grants ( access) {
160166 #[ cfg( feature = "stack-cache" ) ]
@@ -185,8 +191,11 @@ impl<'tcx> Stack {
185191 // If we found the tag, look up its position in the stack to see if it grants
186192 // the required permission
187193 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.
190199 if cache_idx == 1 {
191200 self . cache . tags . swap ( 0 , 1 ) ;
192201 self . cache . idx . swap ( 0 , 1 ) ;
@@ -287,7 +296,6 @@ impl<'tcx> Stack {
287296 let unique_range = 0 ..self . len ( ) ;
288297
289298 if disable_start <= unique_range. end {
290- // add 1 so we don't disable the granting item
291299 let lower = unique_range. start . max ( disable_start) ;
292300 let upper = ( unique_range. end + 1 ) . min ( self . borrows . len ( ) ) ;
293301 for item in & mut self . borrows [ lower..upper] {
0 commit comments