DependencyScanningFilesystem.h revision 360784
1//===- DependencyScanningFilesystem.h - clang-scan-deps fs ===---*- C++ -*-===//
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
9#ifndef LLVM_CLANG_TOOLING_DEPENDENCY_SCANNING_FILESYSTEM_H
10#define LLVM_CLANG_TOOLING_DEPENDENCY_SCANNING_FILESYSTEM_H
11
12#include "clang/Basic/LLVM.h"
13#include "clang/Lex/PreprocessorExcludedConditionalDirectiveSkipMapping.h"
14#include "llvm/ADT/StringMap.h"
15#include "llvm/ADT/StringSet.h"
16#include "llvm/Support/Allocator.h"
17#include "llvm/Support/ErrorOr.h"
18#include "llvm/Support/VirtualFileSystem.h"
19#include <mutex>
20
21namespace clang {
22namespace tooling {
23namespace dependencies {
24
25/// An in-memory representation of a file system entity that is of interest to
26/// the dependency scanning filesystem.
27///
28/// It represents one of the following:
29/// - an opened source file with minimized contents and a stat value.
30/// - an opened source file with original contents and a stat value.
31/// - a directory entry with its stat value.
32/// - an error value to represent a file system error.
33/// - a placeholder with an invalid stat indicating a not yet initialized entry.
34class CachedFileSystemEntry {
35public:
36  /// Default constructor creates an entry with an invalid stat.
37  CachedFileSystemEntry() : MaybeStat(llvm::vfs::Status()) {}
38
39  CachedFileSystemEntry(std::error_code Error) : MaybeStat(std::move(Error)) {}
40
41  /// Create an entry that represents an opened source file with minimized or
42  /// original contents.
43  ///
44  /// The filesystem opens the file even for `stat` calls open to avoid the
45  /// issues with stat + open of minimized files that might lead to a
46  /// mismatching size of the file. If file is not minimized, the full file is
47  /// read and copied into memory to ensure that it's not memory mapped to avoid
48  /// running out of file descriptors.
49  static CachedFileSystemEntry createFileEntry(StringRef Filename,
50                                               llvm::vfs::FileSystem &FS,
51                                               bool Minimize = true);
52
53  /// Create an entry that represents a directory on the filesystem.
54  static CachedFileSystemEntry createDirectoryEntry(llvm::vfs::Status &&Stat);
55
56  /// \returns True if the entry is valid.
57  bool isValid() const { return !MaybeStat || MaybeStat->isStatusKnown(); }
58
59  /// \returns True if the current entry points to a directory.
60  bool isDirectory() const { return MaybeStat && MaybeStat->isDirectory(); }
61
62  /// \returns The error or the file's contents.
63  llvm::ErrorOr<StringRef> getContents() const {
64    if (!MaybeStat)
65      return MaybeStat.getError();
66    assert(!MaybeStat->isDirectory() && "not a file");
67    assert(isValid() && "not initialized");
68    return StringRef(Contents);
69  }
70
71  /// \returns The error or the status of the entry.
72  llvm::ErrorOr<llvm::vfs::Status> getStatus() const {
73    assert(isValid() && "not initialized");
74    return MaybeStat;
75  }
76
77  /// \returns the name of the file.
78  StringRef getName() const {
79    assert(isValid() && "not initialized");
80    return MaybeStat->getName();
81  }
82
83  /// Return the mapping between location -> distance that is used to speed up
84  /// the block skipping in the preprocessor.
85  const PreprocessorSkippedRangeMapping &getPPSkippedRangeMapping() const {
86    return PPSkippedRangeMapping;
87  }
88
89  CachedFileSystemEntry(CachedFileSystemEntry &&) = default;
90  CachedFileSystemEntry &operator=(CachedFileSystemEntry &&) = default;
91
92  CachedFileSystemEntry(const CachedFileSystemEntry &) = delete;
93  CachedFileSystemEntry &operator=(const CachedFileSystemEntry &) = delete;
94
95private:
96  llvm::ErrorOr<llvm::vfs::Status> MaybeStat;
97  // Store the contents in a small string to allow a
98  // move from the small string for the minimized contents.
99  // Note: small size of 1 allows us to store an empty string with an implicit
100  // null terminator without any allocations.
101  llvm::SmallString<1> Contents;
102  PreprocessorSkippedRangeMapping PPSkippedRangeMapping;
103};
104
105/// This class is a shared cache, that caches the 'stat' and 'open' calls to the
106/// underlying real file system.
107///
108/// It is sharded based on the hash of the key to reduce the lock contention for
109/// the worker threads.
110class DependencyScanningFilesystemSharedCache {
111public:
112  struct SharedFileSystemEntry {
113    std::mutex ValueLock;
114    CachedFileSystemEntry Value;
115  };
116
117  DependencyScanningFilesystemSharedCache();
118
119  /// Returns a cache entry for the corresponding key.
120  ///
121  /// A new cache entry is created if the key is not in the cache. This is a
122  /// thread safe call.
123  SharedFileSystemEntry &get(StringRef Key);
124
125private:
126  struct CacheShard {
127    std::mutex CacheLock;
128    llvm::StringMap<SharedFileSystemEntry, llvm::BumpPtrAllocator> Cache;
129  };
130  std::unique_ptr<CacheShard[]> CacheShards;
131  unsigned NumShards;
132};
133
134/// A virtual file system optimized for the dependency discovery.
135///
136/// It is primarily designed to work with source files whose contents was was
137/// preprocessed to remove any tokens that are unlikely to affect the dependency
138/// computation.
139///
140/// This is not a thread safe VFS. A single instance is meant to be used only in
141/// one thread. Multiple instances are allowed to service multiple threads
142/// running in parallel.
143class DependencyScanningWorkerFilesystem : public llvm::vfs::ProxyFileSystem {
144public:
145  DependencyScanningWorkerFilesystem(
146      DependencyScanningFilesystemSharedCache &SharedCache,
147      IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS,
148      ExcludedPreprocessorDirectiveSkipMapping *PPSkipMappings)
149      : ProxyFileSystem(std::move(FS)), SharedCache(SharedCache),
150        PPSkipMappings(PPSkipMappings) {}
151
152  llvm::ErrorOr<llvm::vfs::Status> status(const Twine &Path) override;
153  llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>
154  openFileForRead(const Twine &Path) override;
155
156  /// The set of files that should not be minimized.
157  llvm::StringSet<> IgnoredFiles;
158
159private:
160  void setCachedEntry(StringRef Filename, const CachedFileSystemEntry *Entry) {
161    bool IsInserted = Cache.try_emplace(Filename, Entry).second;
162    (void)IsInserted;
163    assert(IsInserted && "local cache is updated more than once");
164  }
165
166  const CachedFileSystemEntry *getCachedEntry(StringRef Filename) {
167    auto It = Cache.find(Filename);
168    return It == Cache.end() ? nullptr : It->getValue();
169  }
170
171  llvm::ErrorOr<const CachedFileSystemEntry *>
172  getOrCreateFileSystemEntry(const StringRef Filename);
173
174  DependencyScanningFilesystemSharedCache &SharedCache;
175  /// The local cache is used by the worker thread to cache file system queries
176  /// locally instead of querying the global cache every time.
177  llvm::StringMap<const CachedFileSystemEntry *, llvm::BumpPtrAllocator> Cache;
178  /// The optional mapping structure which records information about the
179  /// excluded conditional directive skip mappings that are used by the
180  /// currently active preprocessor.
181  ExcludedPreprocessorDirectiveSkipMapping *PPSkipMappings;
182};
183
184} // end namespace dependencies
185} // end namespace tooling
186} // end namespace clang
187
188#endif // LLVM_CLANG_TOOLING_DEPENDENCY_SCANNING_FILESYSTEM_H
189