clang 23.0.0git
DependencyScanningFilesystem.cpp
Go to the documentation of this file.
1//===- DependencyScanningFilesystem.cpp - Optimized Scanning FS -----------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
10#include "llvm/Support/MemoryBuffer.h"
11#include "llvm/Support/Threading.h"
12#include <optional>
13
14using namespace clang;
15using namespace dependencies;
16
17llvm::ErrorOr<DependencyScanningWorkerFilesystem::TentativeEntry>
18DependencyScanningWorkerFilesystem::readFile(StringRef Filename) {
19 // Load the file and its content from the file system.
20 auto MaybeFile = getUnderlyingFS().openFileForRead(Filename);
21 if (!MaybeFile)
22 return MaybeFile.getError();
23 auto File = std::move(*MaybeFile);
24
25 auto MaybeStat = File->status();
26 if (!MaybeStat)
27 return MaybeStat.getError();
28 auto Stat = std::move(*MaybeStat);
29
30 auto MaybeBuffer = File->getBuffer(Stat.getName());
31 if (!MaybeBuffer)
32 return MaybeBuffer.getError();
33 auto Buffer = std::move(*MaybeBuffer);
34
35 // If the file size changed between read and stat, pretend it didn't.
36 if (Stat.getSize() != Buffer->getBufferSize())
37 Stat = llvm::vfs::Status::copyWithNewSize(Stat, Buffer->getBufferSize());
38
39 return TentativeEntry(Stat, std::move(Buffer));
40}
41
43 EntryRef Ref) {
44 auto &Entry = Ref.Entry;
45
46 if (Entry.isError() || Entry.isDirectory())
47 return false;
48
49 CachedFileContents *Contents = Entry.getCachedContents();
50 assert(Contents && "contents not initialized");
51
52 // Double-checked locking.
53 if (Contents->DepDirectives.load())
54 return true;
55
56 std::lock_guard<std::mutex> GuardLock(Contents->ValueLock);
57
58 // Double-checked locking.
59 if (Contents->DepDirectives.load())
60 return true;
61
63 // Scan the file for preprocessor directives that might affect the
64 // dependencies.
65 if (scanSourceForDependencyDirectives(Contents->Original->getBuffer(),
66 Contents->DepDirectiveTokens,
67 Directives)) {
68 Contents->DepDirectiveTokens.clear();
69 // FIXME: Propagate the diagnostic if desired by the client.
70 Contents->DepDirectives.store(new std::optional<DependencyDirectivesTy>());
71 return false;
72 }
73
74 // This function performed double-checked locking using `DepDirectives`.
75 // Assigning it must be the last thing this function does, otherwise other
76 // threads may skip the critical section (`DepDirectives != nullptr`), leading
77 // to a data race.
78 Contents->DepDirectives.store(
79 new std::optional<DependencyDirectivesTy>(std::move(Directives)));
80 return true;
81}
82
85 // This heuristic was chosen using a empirical testing on a
86 // reasonably high core machine (iMacPro 18 cores / 36 threads). The cache
87 // sharding gives a performance edge by reducing the lock contention.
88 // FIXME: A better heuristic might also consider the OS to account for
89 // the different cost of lock contention on different OSes.
90 NumShards =
91 std::max(2u, llvm::hardware_concurrency().compute_thread_count() / 4);
92 CacheShards = std::make_unique<CacheShard[]>(NumShards);
93}
94
97 StringRef Filename) const {
98 assert(llvm::sys::path::is_absolute_gnu(Filename));
99 return CacheShards[llvm::hash_value(Filename) % NumShards];
100}
101
104 llvm::sys::fs::UniqueID UID) const {
105 auto Hash = llvm::hash_combine(UID.getDevice(), UID.getFile());
106 return CacheShards[Hash % NumShards];
107}
108
109std::vector<DependencyScanningFilesystemSharedCache::OutOfDateEntry>
111 llvm::vfs::FileSystem &UnderlyingFS) const {
112 // Iterate through all shards and look for cached stat errors.
113 std::vector<OutOfDateEntry> InvalidDiagInfo;
114 for (unsigned i = 0; i < NumShards; i++) {
115 const CacheShard &Shard = CacheShards[i];
116 std::lock_guard<std::mutex> LockGuard(Shard.CacheLock);
117 for (const auto &[Path, CachedPair] : Shard.CacheByFilename) {
118 const CachedFileSystemEntry *Entry = CachedPair.first;
119 llvm::ErrorOr<llvm::vfs::Status> Status = UnderlyingFS.status(Path);
120 if (Status) {
121 if (Entry->getError()) {
122 // This is the case where we have cached the non-existence
123 // of the file at Path first, and a file at the path is created
124 // later. The cache entry is not invalidated (as we have no good
125 // way to do it now), which may lead to missing file build errors.
126 InvalidDiagInfo.emplace_back(Path.data());
127 } else {
128 llvm::vfs::Status CachedStatus = Entry->getStatus();
129 if (Status->getType() == llvm::sys::fs::file_type::regular_file &&
130 Status->getType() == CachedStatus.getType()) {
131 // We only check regular files. Directory files sizes could change
132 // due to content changes, and reporting directory size changes can
133 // lead to false positives.
134 // TODO: At the moment, we do not detect symlinks to files whose
135 // size may change. We need to decide if we want to detect cached
136 // symlink size changes. We can also expand this to detect file
137 // type changes.
138 uint64_t CachedSize = CachedStatus.getSize();
139 uint64_t ActualSize = Status->getSize();
140 if (CachedSize != ActualSize) {
141 // This is the case where the cached file has a different size
142 // from the actual file that comes from the underlying FS.
143 InvalidDiagInfo.emplace_back(Path.data(), CachedSize, ActualSize);
144 }
145 }
146 }
147 }
148 }
149 }
150 return InvalidDiagInfo;
151}
152
155 StringRef Filename) const {
156 assert(llvm::sys::path::is_absolute_gnu(Filename));
157 std::lock_guard<std::mutex> LockGuard(CacheLock);
158 auto It = CacheByFilename.find(Filename);
159 return It == CacheByFilename.end() ? nullptr : It->getValue().first;
160}
161
164 llvm::sys::fs::UniqueID UID) const {
165 std::lock_guard<std::mutex> LockGuard(CacheLock);
166 auto It = EntriesByUID.find(UID);
167 return It == EntriesByUID.end() ? nullptr : It->getSecond();
168}
169
172 getOrEmplaceEntryForFilename(StringRef Filename,
173 llvm::ErrorOr<llvm::vfs::Status> Stat) {
174 std::lock_guard<std::mutex> LockGuard(CacheLock);
175 auto [It, Inserted] = CacheByFilename.insert({Filename, {nullptr, nullptr}});
176 auto &[CachedEntry, CachedRealPath] = It->getValue();
177 if (!CachedEntry) {
178 // The entry is not present in the shared cache. Either the cache doesn't
179 // know about the file at all, or it only knows about its real path.
180 assert((Inserted || CachedRealPath) && "existing file with empty pair");
181 CachedEntry =
182 new (EntryStorage.Allocate()) CachedFileSystemEntry(std::move(Stat));
183 }
184 return *CachedEntry;
185}
186
189 llvm::sys::fs::UniqueID UID, llvm::vfs::Status Stat,
190 std::unique_ptr<llvm::MemoryBuffer> Contents) {
191 std::lock_guard<std::mutex> LockGuard(CacheLock);
192 auto [It, Inserted] = EntriesByUID.try_emplace(UID);
193 auto &CachedEntry = It->getSecond();
194 if (Inserted) {
195 CachedFileContents *StoredContents = nullptr;
196 if (Contents)
197 StoredContents = new (ContentsStorage.Allocate())
198 CachedFileContents(std::move(Contents));
199 CachedEntry = new (EntryStorage.Allocate())
200 CachedFileSystemEntry(std::move(Stat), StoredContents);
201 }
202 return *CachedEntry;
203}
204
207 getOrInsertEntryForFilename(StringRef Filename,
208 const CachedFileSystemEntry &Entry) {
209 std::lock_guard<std::mutex> LockGuard(CacheLock);
210 auto [It, Inserted] = CacheByFilename.insert({Filename, {&Entry, nullptr}});
211 auto &[CachedEntry, CachedRealPath] = It->getValue();
212 if (!Inserted || !CachedEntry)
213 CachedEntry = &Entry;
214 return *CachedEntry;
215}
216
217const CachedRealPath *
219 StringRef Filename) const {
220 assert(llvm::sys::path::is_absolute_gnu(Filename));
221 std::lock_guard<std::mutex> LockGuard(CacheLock);
222 auto It = CacheByFilename.find(Filename);
223 return It == CacheByFilename.end() ? nullptr : It->getValue().second;
224}
225
227 getOrEmplaceRealPathForFilename(StringRef Filename,
228 llvm::ErrorOr<llvm::StringRef> RealPath) {
229 std::lock_guard<std::mutex> LockGuard(CacheLock);
230
231 const CachedRealPath *&StoredRealPath = CacheByFilename[Filename].second;
232 if (!StoredRealPath) {
233 auto OwnedRealPath = [&]() -> CachedRealPath {
234 if (!RealPath)
235 return RealPath.getError();
236 return RealPath->str();
237 }();
238
239 StoredRealPath = new (RealPathStorage.Allocate())
240 CachedRealPath(std::move(OwnedRealPath));
241 }
242
243 return *StoredRealPath;
244}
245
250 llvm::vfs::ProxyFileSystem>(std::move(FS)),
251 SharedCache(SharedCache),
252 WorkingDirForCacheLookup(llvm::errc::invalid_argument) {
253 updateWorkingDirForCacheLookup();
254}
255
257DependencyScanningWorkerFilesystem::getOrEmplaceSharedEntryForUID(
258 TentativeEntry TEntry) {
259 auto &Shard = SharedCache.getShardForUID(TEntry.Status.getUniqueID());
260 return Shard.getOrEmplaceEntryForUID(TEntry.Status.getUniqueID(),
261 std::move(TEntry.Status),
262 std::move(TEntry.Contents));
263}
264
266DependencyScanningWorkerFilesystem::findEntryByFilenameWithWriteThrough(
267 StringRef Filename) {
268 if (const auto *Entry = LocalCache.findEntryByFilename(Filename))
269 return Entry;
270 auto &Shard = SharedCache.getShardForFilename(Filename);
271 if (const auto *Entry = Shard.findEntryByFilename(Filename))
272 return &LocalCache.insertEntryForFilename(Filename, *Entry);
273 return nullptr;
274}
275
276llvm::ErrorOr<const CachedFileSystemEntry &>
277DependencyScanningWorkerFilesystem::computeAndStoreResult(
278 StringRef OriginalFilename, StringRef FilenameForLookup) {
279 llvm::ErrorOr<llvm::vfs::Status> Stat =
280 getUnderlyingFS().status(OriginalFilename);
281 if (!Stat) {
282 const auto &Entry =
283 getOrEmplaceSharedEntryForFilename(FilenameForLookup, Stat.getError());
284 return insertLocalEntryForFilename(FilenameForLookup, Entry);
285 }
286
287 if (const auto *Entry = findSharedEntryByUID(*Stat))
288 return insertLocalEntryForFilename(FilenameForLookup, *Entry);
289
290 auto TEntry =
291 Stat->isDirectory() ? TentativeEntry(*Stat) : readFile(OriginalFilename);
292
293 const CachedFileSystemEntry *SharedEntry = [&]() {
294 if (TEntry) {
295 const auto &UIDEntry = getOrEmplaceSharedEntryForUID(std::move(*TEntry));
296 return &getOrInsertSharedEntryForFilename(FilenameForLookup, UIDEntry);
297 }
298 return &getOrEmplaceSharedEntryForFilename(FilenameForLookup,
299 TEntry.getError());
300 }();
301
302 return insertLocalEntryForFilename(FilenameForLookup, *SharedEntry);
303}
304
305llvm::ErrorOr<EntryRef>
307 StringRef OriginalFilename) {
308 SmallString<256> PathBuf;
309 auto FilenameForLookup = tryGetFilenameForLookup(OriginalFilename, PathBuf);
310 if (!FilenameForLookup)
311 return FilenameForLookup.getError();
312
313 if (const auto *Entry =
314 findEntryByFilenameWithWriteThrough(*FilenameForLookup))
315 return EntryRef(OriginalFilename, *Entry).unwrapError();
316 auto MaybeEntry = computeAndStoreResult(OriginalFilename, *FilenameForLookup);
317 if (!MaybeEntry)
318 return MaybeEntry.getError();
319 return EntryRef(OriginalFilename, *MaybeEntry).unwrapError();
320}
321
322llvm::ErrorOr<llvm::vfs::Status>
324 SmallString<256> OwnedFilename;
325 StringRef Filename = Path.toStringRef(OwnedFilename);
326
327 llvm::ErrorOr<EntryRef> Result = getOrCreateFileSystemEntry(Filename);
328 if (!Result)
329 return Result.getError();
330 return Result->getStatus();
331}
332
334 // While some VFS overlay filesystems may implement more-efficient
335 // mechanisms for `exists` queries, `DependencyScanningWorkerFilesystem`
336 // typically wraps `RealFileSystem` which does not specialize `exists`,
337 // so it is not likely to benefit from such optimizations. Instead,
338 // it is more-valuable to have this query go through the
339 // cached-`status` code-path of the `DependencyScanningWorkerFilesystem`.
340 llvm::ErrorOr<llvm::vfs::Status> Status = status(Path);
341 return Status && Status->exists();
342}
343
344namespace {
345
346/// The VFS that is used by clang consumes the \c CachedFileSystemEntry using
347/// this subclass.
348class DepScanFile final : public llvm::vfs::File {
349public:
350 DepScanFile(std::unique_ptr<llvm::MemoryBuffer> Buffer,
351 llvm::vfs::Status Stat)
352 : Buffer(std::move(Buffer)), Stat(std::move(Stat)) {}
353
354 static llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>> create(EntryRef Entry);
355
356 llvm::ErrorOr<llvm::vfs::Status> status() override { return Stat; }
357
358 llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>
359 getBuffer(const Twine &Name, int64_t FileSize, bool RequiresNullTerminator,
360 bool IsVolatile) override {
361 return llvm::MemoryBuffer::getMemBuffer(Buffer->getMemBufferRef(),
362 RequiresNullTerminator);
363 }
364
365 std::error_code close() override { return {}; }
366
367private:
368 std::unique_ptr<llvm::MemoryBuffer> Buffer;
369 llvm::vfs::Status Stat;
370};
371
372} // end anonymous namespace
373
374llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>
375DepScanFile::create(EntryRef Entry) {
376 assert(!Entry.isError() && "error");
377
378 if (Entry.isDirectory())
379 return std::make_error_code(std::errc::is_a_directory);
380
381 auto Result = std::make_unique<DepScanFile>(
382 llvm::MemoryBuffer::getMemBuffer(Entry.getContents(),
383 Entry.getStatus().getName(),
384 /*RequiresNullTerminator=*/false),
385 Entry.getStatus());
386
387 return llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>(
388 std::unique_ptr<llvm::vfs::File>(std::move(Result)));
389}
390
391llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>
393 SmallString<256> OwnedFilename;
394 StringRef Filename = Path.toStringRef(OwnedFilename);
395
396 llvm::ErrorOr<EntryRef> Result = getOrCreateFileSystemEntry(Filename);
397 if (!Result)
398 return Result.getError();
399 return DepScanFile::create(Result.get());
400}
401
402std::error_code
404 SmallVectorImpl<char> &Output) {
405 SmallString<256> OwnedFilename;
406 StringRef OriginalFilename = Path.toStringRef(OwnedFilename);
407
408 SmallString<256> PathBuf;
409 auto FilenameForLookup = tryGetFilenameForLookup(OriginalFilename, PathBuf);
410 if (!FilenameForLookup)
411 return FilenameForLookup.getError();
412
413 auto HandleCachedRealPath =
414 [&Output](const CachedRealPath &RealPath) -> std::error_code {
415 if (!RealPath)
416 return RealPath.getError();
417 Output.assign(RealPath->begin(), RealPath->end());
418 return {};
419 };
420
421 // If we already have the result in local cache, no work required.
422 if (const auto *RealPath =
423 LocalCache.findRealPathByFilename(*FilenameForLookup))
424 return HandleCachedRealPath(*RealPath);
425
426 // If we have the result in the shared cache, cache it locally.
427 auto &Shard = SharedCache.getShardForFilename(*FilenameForLookup);
428 if (const auto *ShardRealPath =
429 Shard.findRealPathByFilename(*FilenameForLookup)) {
430 const auto &RealPath = LocalCache.insertRealPathForFilename(
431 *FilenameForLookup, *ShardRealPath);
432 return HandleCachedRealPath(RealPath);
433 }
434
435 // If we don't know the real path, compute it...
436 std::error_code EC = getUnderlyingFS().getRealPath(OriginalFilename, Output);
437 llvm::ErrorOr<llvm::StringRef> ComputedRealPath = EC;
438 if (!EC)
439 ComputedRealPath = StringRef{Output.data(), Output.size()};
440
441 // ...and try to write it into the shared cache. In case some other thread won
442 // this race and already wrote its own result there, just adopt it. Write
443 // whatever is in the shared cache into the local one.
444 const auto &RealPath = Shard.getOrEmplaceRealPathForFilename(
445 *FilenameForLookup, ComputedRealPath);
446 return HandleCachedRealPath(
447 LocalCache.insertRealPathForFilename(*FilenameForLookup, RealPath));
448}
449
451 const Twine &Path) {
452 std::error_code EC = ProxyFileSystem::setCurrentWorkingDirectory(Path);
453 updateWorkingDirForCacheLookup();
454 return EC;
455}
456
457void DependencyScanningWorkerFilesystem::updateWorkingDirForCacheLookup() {
458 llvm::ErrorOr<std::string> CWD =
459 getUnderlyingFS().getCurrentWorkingDirectory();
460 if (!CWD) {
461 WorkingDirForCacheLookup = CWD.getError();
462 } else if (!llvm::sys::path::is_absolute_gnu(*CWD)) {
463 WorkingDirForCacheLookup = llvm::errc::invalid_argument;
464 } else {
465 WorkingDirForCacheLookup = *CWD;
466 }
467 assert(!WorkingDirForCacheLookup ||
468 llvm::sys::path::is_absolute_gnu(*WorkingDirForCacheLookup));
469}
470
471llvm::ErrorOr<StringRef>
472DependencyScanningWorkerFilesystem::tryGetFilenameForLookup(
473 StringRef OriginalFilename, llvm::SmallVectorImpl<char> &PathBuf) const {
474 StringRef FilenameForLookup;
475 if (llvm::sys::path::is_absolute_gnu(OriginalFilename)) {
476 FilenameForLookup = OriginalFilename;
477 } else if (!WorkingDirForCacheLookup) {
478 return WorkingDirForCacheLookup.getError();
479 } else {
480 StringRef RelFilename = OriginalFilename;
481 RelFilename.consume_front("./");
482 PathBuf.assign(WorkingDirForCacheLookup->begin(),
483 WorkingDirForCacheLookup->end());
484 llvm::sys::path::append(PathBuf, RelFilename);
485 FilenameForLookup = StringRef{PathBuf.begin(), PathBuf.size()};
486 }
487 assert(llvm::sys::path::is_absolute_gnu(FilenameForLookup));
488 return FilenameForLookup;
489}
490
An in-memory representation of a file system entity that is of interest to the dependency scanning fi...
const CachedFileSystemEntry & insertEntryForFilename(StringRef Filename, const CachedFileSystemEntry &Entry)
Associates the given entry with the filename and returns the given entry pointer (for convenience).
const CachedFileSystemEntry * findEntryByFilename(StringRef Filename) const
Returns entry associated with the filename or nullptr if none is found.
This class is a shared cache, that caches the 'stat' and 'open' calls to the underlying real file sys...
CacheShard & getShardForFilename(StringRef Filename) const
Returns shard for the given key.
std::vector< OutOfDateEntry > getOutOfDateEntries(llvm::vfs::FileSystem &UnderlyingFS) const
Visits all cached entries and re-stat an entry using UnderlyingFS to check if the cache contains out-...
std::error_code getRealPath(const Twine &Path, SmallVectorImpl< char > &Output) override
bool ensureDirectiveTokensArePopulated(EntryRef Entry)
Ensure the directive tokens are populated for this file entry.
bool exists(const Twine &Path) override
Check whether Path exists.
llvm::ErrorOr< EntryRef > getOrCreateFileSystemEntry(StringRef Filename)
Returns entry for the given filename.
llvm::ErrorOr< std::unique_ptr< llvm::vfs::File > > openFileForRead(const Twine &Path) override
DependencyScanningWorkerFilesystem(DependencyScanningFilesystemSharedCache &SharedCache, IntrusiveRefCntPtr< llvm::vfs::FileSystem > FS)
std::error_code setCurrentWorkingDirectory(const Twine &Path) override
llvm::ErrorOr< llvm::vfs::Status > status(const Twine &Path) override
Reference to a CachedFileSystemEntry.
llvm::ErrorOr< EntryRef > unwrapError() const
If the cached entry represents an error, promotes it into ErrorOr.
llvm::ErrorOr< std::string > CachedRealPath
The JSON file list parser is used to communicate input to InstallAPI.
nullptr
This class represents a compute construct, representing a 'Kind' of ‘parallel’, 'serial',...
bool scanSourceForDependencyDirectives(StringRef Input, SmallVectorImpl< dependency_directives_scan::Token > &Tokens, SmallVectorImpl< dependency_directives_scan::Directive > &Directives, DiagnosticsEngine *Diags=nullptr, SourceLocation InputSourceLoc=SourceLocation())
Scan the input for the preprocessor directives that might have an effect on the dependencies for a co...
@ Result
The result type of a method or function.
Definition TypeBase.h:905
Diagnostic wrappers for TextAPI types for error reporting.
Definition Dominators.h:30
hash_code hash_value(const clang::dependencies::ModuleID &ID)
Contents and directive tokens of a cached file entry.
std::unique_ptr< llvm::MemoryBuffer > Original
Owning storage for the original contents.
SmallVector< dependency_directives_scan::Token, 10 > DepDirectiveTokens
std::atomic< const std::optional< DependencyDirectivesTy > * > DepDirectives
Accessor to the directive tokens that's atomic to avoid data races.
std::mutex ValueLock
The mutex that must be locked before mutating directive tokens.
const CachedFileSystemEntry & getOrEmplaceEntryForUID(llvm::sys::fs::UniqueID UID, llvm::vfs::Status Stat, std::unique_ptr< llvm::MemoryBuffer > Contents)
Returns entry associated with the unique ID if there is some.
llvm::SpecificBumpPtrAllocator< CachedFileSystemEntry > EntryStorage
The backing storage for cached entries.
llvm::SpecificBumpPtrAllocator< CachedFileContents > ContentsStorage
The backing storage for cached contents.
llvm::SpecificBumpPtrAllocator< CachedRealPath > RealPathStorage
The backing storage for cached real paths.
const CachedFileSystemEntry * findEntryByUID(llvm::sys::fs::UniqueID UID) const
Returns entry associated with the unique ID or nullptr if none is found.
const CachedRealPath * findRealPathByFilename(StringRef Filename) const
Returns the real path associated with the filename or nullptr if none is found.
std::mutex CacheLock
The mutex that needs to be locked before mutation of any member.
const CachedFileSystemEntry & getOrInsertEntryForFilename(StringRef Filename, const CachedFileSystemEntry &Entry)
Returns entry associated with the filename if there is some.
const CachedFileSystemEntry * findEntryByFilename(StringRef Filename) const
Returns entry associated with the filename or nullptr if none is found.
const CachedFileSystemEntry & getOrEmplaceEntryForFilename(StringRef Filename, llvm::ErrorOr< llvm::vfs::Status > Stat)
Returns entry associated with the filename if there is some.
llvm::StringMap< std::pair< const CachedFileSystemEntry *, const CachedRealPath * >, llvm::BumpPtrAllocator > CacheByFilename
Map from filenames to cached entries and real paths.
const CachedRealPath & getOrEmplaceRealPathForFilename(StringRef Filename, llvm::ErrorOr< StringRef > RealPath)
Returns the real path associated with the filename if there is some.
llvm::DenseMap< llvm::sys::fs::UniqueID, const CachedFileSystemEntry * > EntriesByUID
Map from unique IDs to cached entries.