Skip to content

Commit e8b5551

Browse files
committed
Change drop parameter in LookupCache and set a cap to prevent overgrowing cache. Refactoring.
1 parent 703d83a commit e8b5551

File tree

6 files changed

+94
-84
lines changed

6 files changed

+94
-84
lines changed

Sources/SwiftLexicalLookup/LookupCache.swift

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -22,34 +22,24 @@ public class LookupCache {
2222
/// Identified by `SyntaxIdentifier`.
2323
private let sequentialResultsCache: LRUCache<SyntaxIdentifier, [LookupResult]>
2424
/// Looked-up scope identifiers during cache accesses.
25-
private var hits: Set<SyntaxIdentifier> = []
26-
27-
private let dropMod: Int
25+
private var hits: Set<SyntaxIdentifier> = [] {
26+
didSet {
27+
if hits.count > capacity * 2 {
28+
hits.removeAll()
29+
}
30+
}
31+
}
32+
33+
private let capacity: Int
2834
private var evictionCount = 0
2935

3036
/// Creates a new unqualified lookup cache.
3137
/// `capacity` describes the maximum amount of entries in the cache.
3238
/// The cache size is maintained according to the LRU (Least Recently Used) policy.
33-
/// `drop` parameter specifies how many eviction calls will be
34-
/// ignored before evicting not-hit members from subsequent lookups.
35-
///
36-
/// Example cache eviction sequences (s - skip, e - evict):
37-
/// - `drop = 0` - `e -> e -> e -> e -> e -> ...`
38-
/// - `drop = 1` - `s -> e -> s -> s -> e -> ...`
39-
/// - `drop = 3` - `s -> s -> s -> e -> s -> ...`
40-
///
41-
/// - Note: `drop = 0` effectively maintains exactly one path of cached results to
42-
/// the root in the cache (assuming we evict cache members after each lookup in a sequence of lookups).
43-
/// Higher the `drop` value, more such paths can potentially be stored in the cache at any given moment.
44-
/// Because of that, a higher `drop` value also translates to a higher number of cache-hits,
45-
/// but it might not directly translate to better performance. Because of a larger memory footprint,
46-
/// memory accesses could take longer, slowing down the eviction process. That's why the `drop` value
47-
/// could be fine-tuned to maximize the performance given file size,
48-
/// number of lookups, and amount of available memory.
49-
public init(capacity: Int, drop: Int = 0) {
39+
public init(capacity: Int) {
40+
self.capacity = capacity
5041
self.ancestorResultsCache = LRUCache(capacity: (capacity + 1) / 2)
5142
self.sequentialResultsCache = LRUCache(capacity: capacity / 2)
52-
self.dropMod = drop + 1
5343
}
5444

5545
/// Get cached ancestor results for the given `id`.
@@ -86,17 +76,31 @@ public class LookupCache {
8676
}
8777

8878
/// Removes all cached entries without a hit, unless it's prohibited
89-
/// by the internal drop counter (as specified by `drop` in the initializer).
90-
/// The dropping behavior can be disabled for this call with the `bypassDropCounter`
91-
/// parameter, resulting in immediate eviction of entries without a hit.
92-
public func evictEntriesWithoutHit(bypassDropCounter: Bool = false) {
93-
if !bypassDropCounter {
94-
evictionCount = (evictionCount + 1) % dropMod
95-
guard evictionCount != 0 else { return }
96-
}
79+
/// by the internal `drop` counter.
80+
/// `drop` parameter specifies how many eviction calls will be
81+
/// ignored before evicting not-hit members from subsequent lookups.
82+
///
83+
/// Example cache eviction sequences (s - skip, e - evict):
84+
/// - `drop = 0` - `e -> e -> e -> e -> e -> ...`
85+
/// - `drop = 1` - `s -> e -> s -> s -> e -> ...`
86+
/// - `drop = 3` - `s -> s -> s -> e -> s -> ...`
87+
///
88+
/// - Note: `drop = 0` effectively maintains exactly one path of cached results to
89+
/// the root in the cache (assuming we evict cache members after each lookup in a sequence of lookups).
90+
/// Higher the `drop` value, more such paths can potentially be stored in the cache at any given moment.
91+
/// Because of that, a higher `drop` value also translates to a higher number of cache-hits,
92+
/// but it might not directly translate to better performance. Because of a larger memory footprint,
93+
/// memory accesses could take longer, slowing down the eviction process. That's why the `drop` value
94+
/// could be fine-tuned to maximize the performance given file size,
95+
/// number of lookups, and amount of available memory.
96+
public func evictEntriesWithoutHit(drop: Int = 0) {
97+
evictionCount = (evictionCount + 1) % (drop + 1)
98+
guard evictionCount != 0 else { return }
9799

98-
for key in ancestorResultsCache.keysInCache.union(sequentialResultsCache.keysInCache).subtracting(hits) {
100+
for key in ancestorResultsCache.keys where !hits.contains(key) {
99101
ancestorResultsCache[key] = nil
102+
}
103+
for key in sequentialResultsCache.keys where !hits.contains(key) {
100104
sequentialResultsCache[key] = nil
101105
}
102106

Sources/SwiftLexicalLookup/Scopes/ScopeImplementations.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,9 @@ import SwiftSyntax
379379
with config: LookupConfig,
380380
cache: LookupCache?
381381
) -> [LookupResult] {
382-
// We're not using `lookupParent` to not cache the results here.
382+
// We're not using `lookupParent` to not cache sequential parent results here.
383+
// Sequential scope might filter out specific names based on the location within this guard stmt
384+
// in subsequent lookups. We're relying on cache of the parent sequential scope instead.
383385
parentScope?.lookup(identifier, at: lookUpPosition, with: config, cache: cache) ?? []
384386
}
385387
}
@@ -732,7 +734,7 @@ import SwiftSyntax
732734
/// the function returns exactly two results. First associated with the member
733735
/// block that consists of the `associatedtype A` declaration and
734736
/// the latter one from the file scope and `class A` exactly in this order.
735-
public func lookup(
737+
@_spi(Experimental) public func lookup(
736738
_ identifier: Identifier?,
737739
at lookUpPosition: AbsolutePosition,
738740
with config: LookupConfig,
@@ -995,7 +997,9 @@ extension SubscriptDeclSyntax: WithGenericParametersScopeSyntax, CanInterleaveRe
995997
cache: cache
996998
)
997999
} else {
998-
// We're not using `lookupParent` to not cache the results here.
1000+
// We're not using `lookupParent` to not cache sequential parent results here.
1001+
// Sequential scope might filter out specific names based on the location within this variable decl
1002+
// in subsequent lookups. We're relying on cache of the parent sequential scope instead.
9991003
return parentScope?.lookup(identifier, at: lookUpPosition, with: config, cache: cache) ?? []
10001004
}
10011005
}
@@ -1123,7 +1127,9 @@ extension SubscriptDeclSyntax: WithGenericParametersScopeSyntax, CanInterleaveRe
11231127
with config: LookupConfig,
11241128
cache: LookupCache?
11251129
) -> [LookupResult] {
1126-
// We're not using `lookupParent` to not cache the results here.
1130+
// We're not using `lookupParent` to not cache sequential parent results here.
1131+
// Sequential scope might filter out specific names based on the location within this if config
1132+
// in subsequent lookups. We're relying on cache of the parent sequential scope instead.
11271133
parentScope?.lookup(identifier, at: lookUpPosition, with: config, cache: cache) ?? []
11281134
}
11291135
}

Sources/SwiftLexicalLookup/Scopes/ScopeSyntax.swift

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -50,57 +50,57 @@ extension SyntaxProtocol {
5050
with config: LookupConfig = LookupConfig(),
5151
cache: LookupCache? = nil
5252
) -> [LookupResult] {
53-
if let cache, let identifier {
54-
let filteredResult: [LookupResult] = (scope?.lookup(nil, at: self.position, with: config, cache: cache) ?? [])
55-
.compactMap { result in
56-
switch result {
57-
case .fromScope(let syntax, let withNames):
58-
let filteredNames =
59-
withNames
60-
.filter { name in
61-
name.identifier == identifier
62-
}
63-
64-
guard filteredNames.count != 0 else { return nil }
65-
return .fromScope(syntax, withNames: filteredNames)
66-
default:
67-
return result
68-
}
53+
guard let cache, let identifier else {
54+
return scope?.lookup(identifier, at: self.position, with: config, cache: cache) ?? []
55+
}
56+
57+
let filteredResult: [LookupResult] = (scope?.lookup(nil, at: self.position, with: config, cache: cache) ?? [])
58+
.compactMap { result in
59+
switch result {
60+
case .fromScope(let syntax, let withNames):
61+
let filteredNames =
62+
withNames
63+
.filter { name in
64+
name.identifier == identifier
65+
}
66+
67+
guard filteredNames.count != 0 else { return nil }
68+
return .fromScope(syntax, withNames: filteredNames)
69+
default:
70+
return result
6971
}
72+
}
73+
74+
var resultWithMergedSequentialResults: [LookupResult] = []
75+
var i = 0
7076

71-
var resultWithMergedSequentialResults: [LookupResult] = []
72-
var i = 0
77+
while i < filteredResult.count {
78+
let thisResult = filteredResult[i]
7379

74-
while i < filteredResult.count {
75-
let thisResult = filteredResult[i]
80+
if i < filteredResult.count - 1,
81+
case .fromScope(let thisScope, let thisNames) = thisResult,
82+
thisScope.asProtocol(SyntaxProtocol.self) is SequentialScopeSyntax
83+
{
84+
var accumulator = thisNames
7685

77-
if i < filteredResult.count - 1,
78-
case .fromScope(let thisScope, let thisNames) = thisResult,
79-
thisScope.asProtocol(SyntaxProtocol.self) is SequentialScopeSyntax
86+
while i < filteredResult.count - 1,
87+
case .fromScope(let otherScope, let otherNames) = filteredResult[i + 1],
88+
otherScope.asProtocol(SyntaxProtocol.self) is SequentialScopeSyntax,
89+
thisScope.id == otherScope.id
8090
{
81-
var accumulator = thisNames
82-
83-
while i < filteredResult.count - 1,
84-
case .fromScope(let otherScope, let otherNames) = filteredResult[i + 1],
85-
otherScope.asProtocol(SyntaxProtocol.self) is SequentialScopeSyntax,
86-
thisScope.id == otherScope.id
87-
{
88-
accumulator += otherNames
89-
i += 1
90-
}
91-
92-
resultWithMergedSequentialResults.append(.fromScope(thisScope, withNames: accumulator))
93-
} else {
94-
resultWithMergedSequentialResults.append(thisResult)
91+
accumulator += otherNames
92+
i += 1
9593
}
9694

97-
i += 1
95+
resultWithMergedSequentialResults.append(.fromScope(thisScope, withNames: accumulator))
96+
} else {
97+
resultWithMergedSequentialResults.append(thisResult)
9898
}
9999

100-
return resultWithMergedSequentialResults
101-
} else {
102-
return scope?.lookup(identifier, at: self.position, with: config, cache: cache) ?? []
100+
i += 1
103101
}
102+
103+
return resultWithMergedSequentialResults
104104
}
105105
}
106106

Sources/SwiftSyntax/LRUCache.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,24 @@ package class LRUCache<Key: Hashable, Value> {
3131
private unowned var head: _Node?
3232
private unowned var tail: _Node?
3333

34-
public let capacity: Int
35-
public private(set) var keysInCache: Set<Key>
34+
package let capacity: Int
3635

37-
public init(capacity: Int) {
36+
package init(capacity: Int) {
3837
self.table = [:]
3938
self.head = nil
4039
self.tail = nil
4140
self.capacity = capacity
42-
self.keysInCache = []
4341
}
4442

45-
public var count: Int {
43+
package var count: Int {
4644
return table.count
4745
}
46+
47+
package var keys: some Collection<Key> {
48+
table.keys
49+
}
4850

49-
public subscript(key: Key) -> Value? {
51+
package subscript(key: Key) -> Value? {
5052
get {
5153
guard let node = table[key] else {
5254
return nil
@@ -61,7 +63,6 @@ package class LRUCache<Key: Hashable, Value> {
6163
self.ensureCapacityForNewValue()
6264
let node = _Node(key: key, value: newValue)
6365
addToHead(node: node)
64-
keysInCache.insert(key)
6566
table[key] = node
6667

6768
case let (node?, newValue?): // update.
@@ -70,7 +71,6 @@ package class LRUCache<Key: Hashable, Value> {
7071

7172
case let (node?, nil): // delete.
7273
remove(node: node)
73-
keysInCache.remove(key)
7474
table[key] = nil
7575

7676
case (nil, nil): // no-op.

Tests/SwiftLexicalLookupTest/Assertions.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ private func testFunction(
147147
let lookupIdentifier = Identifier(tokenAtMarker)
148148

149149
let result = tokenAtMarker.lookup(useNilAsTheParameter ? nil : lookupIdentifier, with: config, cache: cache)
150+
cache?.evictEntriesWithoutHit(drop: 3)
150151

151152
guard let expectedValues = references[marker] else {
152153
XCTFail("For marker \(marker), couldn't find result expectation")

Tests/SwiftCompilerPluginTest/LRUCacheTests.swift renamed to Tests/SwiftSyntaxTest/LRUCacheTests.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
@_spi(Testing) import SwiftCompilerPluginMessageHandling
1413
import SwiftSyntax
1514
import XCTest
1615

0 commit comments

Comments
 (0)