• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
21 namespace clang {
22 namespace tooling {
23 namespace 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.
34 class CachedFileSystemEntry {
35 public:
36   /// Default constructor creates an entry with an invalid stat.
CachedFileSystemEntry()37   CachedFileSystemEntry() : MaybeStat(llvm::vfs::Status()) {}
38 
CachedFileSystemEntry(std::error_code Error)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.
isValid()57   bool isValid() const { return !MaybeStat || MaybeStat->isStatusKnown(); }
58 
59   /// \returns True if the current entry points to a directory.
isDirectory()60   bool isDirectory() const { return MaybeStat && MaybeStat->isDirectory(); }
61 
62   /// \returns The error or the file's contents.
getContents()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.
getStatus()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.
getName()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.
getPPSkippedRangeMapping()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 
95 private:
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.
110 class DependencyScanningFilesystemSharedCache {
111 public:
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 
125 private:
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.
143 class DependencyScanningWorkerFilesystem : public llvm::vfs::ProxyFileSystem {
144 public:
DependencyScanningWorkerFilesystem(DependencyScanningFilesystemSharedCache & SharedCache,IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS,ExcludedPreprocessorDirectiveSkipMapping * PPSkipMappings)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 
159 private:
setCachedEntry(StringRef Filename,const CachedFileSystemEntry * Entry)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 
getCachedEntry(StringRef Filename)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