//===- 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 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 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 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 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 Cache; }; std::unique_ptr 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 FS, ExcludedPreprocessorDirectiveSkipMapping *PPSkipMappings) : ProxyFileSystem(std::move(FS)), SharedCache(SharedCache), PPSkipMappings(PPSkipMappings) {} llvm::ErrorOr status(const Twine &Path) override; llvm::ErrorOr> 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 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 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