| //===- DependencyScanningFilesystem.h - clang-scan-deps fs ===---*- C++ -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef LLVM_CLANG_TOOLING_DEPENDENCY_SCANNING_FILESYSTEM_H |
| #define LLVM_CLANG_TOOLING_DEPENDENCY_SCANNING_FILESYSTEM_H |
| |
| #include "clang/Basic/LLVM.h" |
| #include "clang/Lex/PreprocessorExcludedConditionalDirectiveSkipMapping.h" |
| #include "llvm/ADT/StringMap.h" |
| #include "llvm/ADT/StringSet.h" |
| #include "llvm/Support/Allocator.h" |
| #include "llvm/Support/ErrorOr.h" |
| #include "llvm/Support/VirtualFileSystem.h" |
| #include <mutex> |
| |
| namespace clang { |
| namespace tooling { |
| namespace dependencies { |
| |
| /// An in-memory representation of a file system entity that is of interest to |
| /// the dependency scanning filesystem. |
| /// |
| /// It represents one of the following: |
| /// - an opened source file with minimized contents and a stat value. |
| /// - an opened source file with original contents and a stat value. |
| /// - a directory entry with its stat value. |
| /// - an error value to represent a file system error. |
| /// - a placeholder with an invalid stat indicating a not yet initialized entry. |
| class CachedFileSystemEntry { |
| public: |
| /// Default constructor creates an entry with an invalid stat. |
| CachedFileSystemEntry() : MaybeStat(llvm::vfs::Status()) {} |
| |
| CachedFileSystemEntry(std::error_code Error) : MaybeStat(std::move(Error)) {} |
| |
| /// Create an entry that represents an opened source file with minimized or |
| /// original contents. |
| /// |
| /// The filesystem opens the file even for `stat` calls open to avoid the |
| /// issues with stat + open of minimized files that might lead to a |
| /// mismatching size of the file. If file is not minimized, the full file is |
| /// read and copied into memory to ensure that it's not memory mapped to avoid |
| /// running out of file descriptors. |
| static CachedFileSystemEntry createFileEntry(StringRef Filename, |
| llvm::vfs::FileSystem &FS, |
| bool Minimize = true); |
| |
| /// Create an entry that represents a directory on the filesystem. |
| static CachedFileSystemEntry createDirectoryEntry(llvm::vfs::Status &&Stat); |
| |
| /// \returns True if the entry is valid. |
| bool isValid() const { return !MaybeStat || MaybeStat->isStatusKnown(); } |
| |
| /// \returns True if the current entry points to a directory. |
| bool isDirectory() const { return MaybeStat && MaybeStat->isDirectory(); } |
| |
| /// \returns The error or the file's contents. |
| llvm::ErrorOr<StringRef> getContents() const { |
| if (!MaybeStat) |
| return MaybeStat.getError(); |
| assert(!MaybeStat->isDirectory() && "not a file"); |
| assert(isValid() && "not initialized"); |
| return StringRef(Contents); |
| } |
| |
| /// \returns The error or the status of the entry. |
| llvm::ErrorOr<llvm::vfs::Status> getStatus() const { |
| assert(isValid() && "not initialized"); |
| return MaybeStat; |
| } |
| |
| /// \returns the name of the file. |
| StringRef getName() const { |
| assert(isValid() && "not initialized"); |
| return MaybeStat->getName(); |
| } |
| |
| /// Return the mapping between location -> distance that is used to speed up |
| /// the block skipping in the preprocessor. |
| const PreprocessorSkippedRangeMapping &getPPSkippedRangeMapping() const { |
| return PPSkippedRangeMapping; |
| } |
| |
| CachedFileSystemEntry(CachedFileSystemEntry &&) = default; |
| CachedFileSystemEntry &operator=(CachedFileSystemEntry &&) = default; |
| |
| CachedFileSystemEntry(const CachedFileSystemEntry &) = delete; |
| CachedFileSystemEntry &operator=(const CachedFileSystemEntry &) = delete; |
| |
| private: |
| llvm::ErrorOr<llvm::vfs::Status> MaybeStat; |
| // Store the contents in a small string to allow a |
| // move from the small string for the minimized contents. |
| // Note: small size of 1 allows us to store an empty string with an implicit |
| // null terminator without any allocations. |
| llvm::SmallString<1> Contents; |
| PreprocessorSkippedRangeMapping PPSkippedRangeMapping; |
| }; |
| |
| /// This class is a shared cache, that caches the 'stat' and 'open' calls to the |
| /// underlying real file system. |
| /// |
| /// It is sharded based on the hash of the key to reduce the lock contention for |
| /// the worker threads. |
| class DependencyScanningFilesystemSharedCache { |
| public: |
| struct SharedFileSystemEntry { |
| std::mutex ValueLock; |
| CachedFileSystemEntry Value; |
| }; |
| |
| DependencyScanningFilesystemSharedCache(); |
| |
| /// Returns a cache entry for the corresponding key. |
| /// |
| /// A new cache entry is created if the key is not in the cache. This is a |
| /// thread safe call. |
| SharedFileSystemEntry &get(StringRef Key); |
| |
| private: |
| struct CacheShard { |
| std::mutex CacheLock; |
| llvm::StringMap<SharedFileSystemEntry, llvm::BumpPtrAllocator> Cache; |
| }; |
| std::unique_ptr<CacheShard[]> CacheShards; |
| unsigned NumShards; |
| }; |
| |
| /// A virtual file system optimized for the dependency discovery. |
| /// |
| /// It is primarily designed to work with source files whose contents was was |
| /// preprocessed to remove any tokens that are unlikely to affect the dependency |
| /// computation. |
| /// |
| /// This is not a thread safe VFS. A single instance is meant to be used only in |
| /// one thread. Multiple instances are allowed to service multiple threads |
| /// running in parallel. |
| class DependencyScanningWorkerFilesystem : public llvm::vfs::ProxyFileSystem { |
| public: |
| DependencyScanningWorkerFilesystem( |
| DependencyScanningFilesystemSharedCache &SharedCache, |
| IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS, |
| ExcludedPreprocessorDirectiveSkipMapping *PPSkipMappings) |
| : ProxyFileSystem(std::move(FS)), SharedCache(SharedCache), |
| PPSkipMappings(PPSkipMappings) {} |
| |
| llvm::ErrorOr<llvm::vfs::Status> status(const Twine &Path) override; |
| llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>> |
| openFileForRead(const Twine &Path) override; |
| |
| /// The set of files that should not be minimized. |
| llvm::StringSet<> IgnoredFiles; |
| |
| private: |
| void setCachedEntry(StringRef Filename, const CachedFileSystemEntry *Entry) { |
| bool IsInserted = Cache.try_emplace(Filename, Entry).second; |
| (void)IsInserted; |
| assert(IsInserted && "local cache is updated more than once"); |
| } |
| |
| const CachedFileSystemEntry *getCachedEntry(StringRef Filename) { |
| auto It = Cache.find(Filename); |
| return It == Cache.end() ? nullptr : It->getValue(); |
| } |
| |
| llvm::ErrorOr<const CachedFileSystemEntry *> |
| getOrCreateFileSystemEntry(const StringRef Filename); |
| |
| DependencyScanningFilesystemSharedCache &SharedCache; |
| /// The local cache is used by the worker thread to cache file system queries |
| /// locally instead of querying the global cache every time. |
| llvm::StringMap<const CachedFileSystemEntry *, llvm::BumpPtrAllocator> Cache; |
| /// The optional mapping structure which records information about the |
| /// excluded conditional directive skip mappings that are used by the |
| /// currently active preprocessor. |
| ExcludedPreprocessorDirectiveSkipMapping *PPSkipMappings; |
| }; |
| |
| } // end namespace dependencies |
| } // end namespace tooling |
| } // end namespace clang |
| |
| #endif // LLVM_CLANG_TOOLING_DEPENDENCY_SCANNING_FILESYSTEM_H |