Path: blob/main/contrib/llvm-project/clang/lib/Tooling/DependencyScanning/DependencyScanningFilesystem.cpp
35294 views
//===- DependencyScanningFilesystem.cpp - clang-scan-deps fs --------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include "clang/Tooling/DependencyScanning/DependencyScanningFilesystem.h"9#include "llvm/Support/MemoryBuffer.h"10#include "llvm/Support/SmallVectorMemoryBuffer.h"11#include "llvm/Support/Threading.h"12#include <optional>1314using namespace clang;15using namespace tooling;16using namespace dependencies;1718llvm::ErrorOr<DependencyScanningWorkerFilesystem::TentativeEntry>19DependencyScanningWorkerFilesystem::readFile(StringRef Filename) {20// Load the file and its content from the file system.21auto MaybeFile = getUnderlyingFS().openFileForRead(Filename);22if (!MaybeFile)23return MaybeFile.getError();24auto File = std::move(*MaybeFile);2526auto MaybeStat = File->status();27if (!MaybeStat)28return MaybeStat.getError();29auto Stat = std::move(*MaybeStat);3031auto MaybeBuffer = File->getBuffer(Stat.getName());32if (!MaybeBuffer)33return MaybeBuffer.getError();34auto Buffer = std::move(*MaybeBuffer);3536// If the file size changed between read and stat, pretend it didn't.37if (Stat.getSize() != Buffer->getBufferSize())38Stat = llvm::vfs::Status::copyWithNewSize(Stat, Buffer->getBufferSize());3940return TentativeEntry(Stat, std::move(Buffer));41}4243bool DependencyScanningWorkerFilesystem::ensureDirectiveTokensArePopulated(44EntryRef Ref) {45auto &Entry = Ref.Entry;4647if (Entry.isError() || Entry.isDirectory())48return false;4950CachedFileContents *Contents = Entry.getCachedContents();51assert(Contents && "contents not initialized");5253// Double-checked locking.54if (Contents->DepDirectives.load())55return true;5657std::lock_guard<std::mutex> GuardLock(Contents->ValueLock);5859// Double-checked locking.60if (Contents->DepDirectives.load())61return true;6263SmallVector<dependency_directives_scan::Directive, 64> Directives;64// Scan the file for preprocessor directives that might affect the65// dependencies.66if (scanSourceForDependencyDirectives(Contents->Original->getBuffer(),67Contents->DepDirectiveTokens,68Directives)) {69Contents->DepDirectiveTokens.clear();70// FIXME: Propagate the diagnostic if desired by the client.71Contents->DepDirectives.store(new std::optional<DependencyDirectivesTy>());72return false;73}7475// This function performed double-checked locking using `DepDirectives`.76// Assigning it must be the last thing this function does, otherwise other77// threads may skip the critical section (`DepDirectives != nullptr`), leading78// to a data race.79Contents->DepDirectives.store(80new std::optional<DependencyDirectivesTy>(std::move(Directives)));81return true;82}8384DependencyScanningFilesystemSharedCache::85DependencyScanningFilesystemSharedCache() {86// This heuristic was chosen using a empirical testing on a87// reasonably high core machine (iMacPro 18 cores / 36 threads). The cache88// sharding gives a performance edge by reducing the lock contention.89// FIXME: A better heuristic might also consider the OS to account for90// the different cost of lock contention on different OSes.91NumShards =92std::max(2u, llvm::hardware_concurrency().compute_thread_count() / 4);93CacheShards = std::make_unique<CacheShard[]>(NumShards);94}9596DependencyScanningFilesystemSharedCache::CacheShard &97DependencyScanningFilesystemSharedCache::getShardForFilename(98StringRef Filename) const {99assert(llvm::sys::path::is_absolute_gnu(Filename));100return CacheShards[llvm::hash_value(Filename) % NumShards];101}102103DependencyScanningFilesystemSharedCache::CacheShard &104DependencyScanningFilesystemSharedCache::getShardForUID(105llvm::sys::fs::UniqueID UID) const {106auto Hash = llvm::hash_combine(UID.getDevice(), UID.getFile());107return CacheShards[Hash % NumShards];108}109110const CachedFileSystemEntry *111DependencyScanningFilesystemSharedCache::CacheShard::findEntryByFilename(112StringRef Filename) const {113assert(llvm::sys::path::is_absolute_gnu(Filename));114std::lock_guard<std::mutex> LockGuard(CacheLock);115auto It = CacheByFilename.find(Filename);116return It == CacheByFilename.end() ? nullptr : It->getValue().first;117}118119const CachedFileSystemEntry *120DependencyScanningFilesystemSharedCache::CacheShard::findEntryByUID(121llvm::sys::fs::UniqueID UID) const {122std::lock_guard<std::mutex> LockGuard(CacheLock);123auto It = EntriesByUID.find(UID);124return It == EntriesByUID.end() ? nullptr : It->getSecond();125}126127const CachedFileSystemEntry &128DependencyScanningFilesystemSharedCache::CacheShard::129getOrEmplaceEntryForFilename(StringRef Filename,130llvm::ErrorOr<llvm::vfs::Status> Stat) {131std::lock_guard<std::mutex> LockGuard(CacheLock);132auto [It, Inserted] = CacheByFilename.insert({Filename, {nullptr, nullptr}});133auto &[CachedEntry, CachedRealPath] = It->getValue();134if (!CachedEntry) {135// The entry is not present in the shared cache. Either the cache doesn't136// know about the file at all, or it only knows about its real path.137assert((Inserted || CachedRealPath) && "existing file with empty pair");138CachedEntry =139new (EntryStorage.Allocate()) CachedFileSystemEntry(std::move(Stat));140}141return *CachedEntry;142}143144const CachedFileSystemEntry &145DependencyScanningFilesystemSharedCache::CacheShard::getOrEmplaceEntryForUID(146llvm::sys::fs::UniqueID UID, llvm::vfs::Status Stat,147std::unique_ptr<llvm::MemoryBuffer> Contents) {148std::lock_guard<std::mutex> LockGuard(CacheLock);149auto [It, Inserted] = EntriesByUID.insert({UID, nullptr});150auto &CachedEntry = It->getSecond();151if (Inserted) {152CachedFileContents *StoredContents = nullptr;153if (Contents)154StoredContents = new (ContentsStorage.Allocate())155CachedFileContents(std::move(Contents));156CachedEntry = new (EntryStorage.Allocate())157CachedFileSystemEntry(std::move(Stat), StoredContents);158}159return *CachedEntry;160}161162const CachedFileSystemEntry &163DependencyScanningFilesystemSharedCache::CacheShard::164getOrInsertEntryForFilename(StringRef Filename,165const CachedFileSystemEntry &Entry) {166std::lock_guard<std::mutex> LockGuard(CacheLock);167auto [It, Inserted] = CacheByFilename.insert({Filename, {&Entry, nullptr}});168auto &[CachedEntry, CachedRealPath] = It->getValue();169if (!Inserted || !CachedEntry)170CachedEntry = &Entry;171return *CachedEntry;172}173174const CachedRealPath *175DependencyScanningFilesystemSharedCache::CacheShard::findRealPathByFilename(176StringRef Filename) const {177assert(llvm::sys::path::is_absolute_gnu(Filename));178std::lock_guard<std::mutex> LockGuard(CacheLock);179auto It = CacheByFilename.find(Filename);180return It == CacheByFilename.end() ? nullptr : It->getValue().second;181}182183const CachedRealPath &DependencyScanningFilesystemSharedCache::CacheShard::184getOrEmplaceRealPathForFilename(StringRef Filename,185llvm::ErrorOr<llvm::StringRef> RealPath) {186std::lock_guard<std::mutex> LockGuard(CacheLock);187188const CachedRealPath *&StoredRealPath = CacheByFilename[Filename].second;189if (!StoredRealPath) {190auto OwnedRealPath = [&]() -> CachedRealPath {191if (!RealPath)192return RealPath.getError();193return RealPath->str();194}();195196StoredRealPath = new (RealPathStorage.Allocate())197CachedRealPath(std::move(OwnedRealPath));198}199200return *StoredRealPath;201}202203static bool shouldCacheStatFailures(StringRef Filename) {204StringRef Ext = llvm::sys::path::extension(Filename);205if (Ext.empty())206return false; // This may be the module cache directory.207return true;208}209210DependencyScanningWorkerFilesystem::DependencyScanningWorkerFilesystem(211DependencyScanningFilesystemSharedCache &SharedCache,212IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS)213: llvm::RTTIExtends<DependencyScanningWorkerFilesystem,214llvm::vfs::ProxyFileSystem>(std::move(FS)),215SharedCache(SharedCache),216WorkingDirForCacheLookup(llvm::errc::invalid_argument) {217updateWorkingDirForCacheLookup();218}219220const CachedFileSystemEntry &221DependencyScanningWorkerFilesystem::getOrEmplaceSharedEntryForUID(222TentativeEntry TEntry) {223auto &Shard = SharedCache.getShardForUID(TEntry.Status.getUniqueID());224return Shard.getOrEmplaceEntryForUID(TEntry.Status.getUniqueID(),225std::move(TEntry.Status),226std::move(TEntry.Contents));227}228229const CachedFileSystemEntry *230DependencyScanningWorkerFilesystem::findEntryByFilenameWithWriteThrough(231StringRef Filename) {232if (const auto *Entry = LocalCache.findEntryByFilename(Filename))233return Entry;234auto &Shard = SharedCache.getShardForFilename(Filename);235if (const auto *Entry = Shard.findEntryByFilename(Filename))236return &LocalCache.insertEntryForFilename(Filename, *Entry);237return nullptr;238}239240llvm::ErrorOr<const CachedFileSystemEntry &>241DependencyScanningWorkerFilesystem::computeAndStoreResult(242StringRef OriginalFilename, StringRef FilenameForLookup) {243llvm::ErrorOr<llvm::vfs::Status> Stat =244getUnderlyingFS().status(OriginalFilename);245if (!Stat) {246if (!shouldCacheStatFailures(OriginalFilename))247return Stat.getError();248const auto &Entry =249getOrEmplaceSharedEntryForFilename(FilenameForLookup, Stat.getError());250return insertLocalEntryForFilename(FilenameForLookup, Entry);251}252253if (const auto *Entry = findSharedEntryByUID(*Stat))254return insertLocalEntryForFilename(FilenameForLookup, *Entry);255256auto TEntry =257Stat->isDirectory() ? TentativeEntry(*Stat) : readFile(OriginalFilename);258259const CachedFileSystemEntry *SharedEntry = [&]() {260if (TEntry) {261const auto &UIDEntry = getOrEmplaceSharedEntryForUID(std::move(*TEntry));262return &getOrInsertSharedEntryForFilename(FilenameForLookup, UIDEntry);263}264return &getOrEmplaceSharedEntryForFilename(FilenameForLookup,265TEntry.getError());266}();267268return insertLocalEntryForFilename(FilenameForLookup, *SharedEntry);269}270271llvm::ErrorOr<EntryRef>272DependencyScanningWorkerFilesystem::getOrCreateFileSystemEntry(273StringRef OriginalFilename) {274SmallString<256> PathBuf;275auto FilenameForLookup = tryGetFilenameForLookup(OriginalFilename, PathBuf);276if (!FilenameForLookup)277return FilenameForLookup.getError();278279if (const auto *Entry =280findEntryByFilenameWithWriteThrough(*FilenameForLookup))281return EntryRef(OriginalFilename, *Entry).unwrapError();282auto MaybeEntry = computeAndStoreResult(OriginalFilename, *FilenameForLookup);283if (!MaybeEntry)284return MaybeEntry.getError();285return EntryRef(OriginalFilename, *MaybeEntry).unwrapError();286}287288llvm::ErrorOr<llvm::vfs::Status>289DependencyScanningWorkerFilesystem::status(const Twine &Path) {290SmallString<256> OwnedFilename;291StringRef Filename = Path.toStringRef(OwnedFilename);292293if (Filename.ends_with(".pcm"))294return getUnderlyingFS().status(Path);295296llvm::ErrorOr<EntryRef> Result = getOrCreateFileSystemEntry(Filename);297if (!Result)298return Result.getError();299return Result->getStatus();300}301302bool DependencyScanningWorkerFilesystem::exists(const Twine &Path) {303// While some VFS overlay filesystems may implement more-efficient304// mechanisms for `exists` queries, `DependencyScanningWorkerFilesystem`305// typically wraps `RealFileSystem` which does not specialize `exists`,306// so it is not likely to benefit from such optimizations. Instead,307// it is more-valuable to have this query go through the308// cached-`status` code-path of the `DependencyScanningWorkerFilesystem`.309llvm::ErrorOr<llvm::vfs::Status> Status = status(Path);310return Status && Status->exists();311}312313namespace {314315/// The VFS that is used by clang consumes the \c CachedFileSystemEntry using316/// this subclass.317class DepScanFile final : public llvm::vfs::File {318public:319DepScanFile(std::unique_ptr<llvm::MemoryBuffer> Buffer,320llvm::vfs::Status Stat)321: Buffer(std::move(Buffer)), Stat(std::move(Stat)) {}322323static llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>> create(EntryRef Entry);324325llvm::ErrorOr<llvm::vfs::Status> status() override { return Stat; }326327llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>328getBuffer(const Twine &Name, int64_t FileSize, bool RequiresNullTerminator,329bool IsVolatile) override {330return std::move(Buffer);331}332333std::error_code close() override { return {}; }334335private:336std::unique_ptr<llvm::MemoryBuffer> Buffer;337llvm::vfs::Status Stat;338};339340} // end anonymous namespace341342llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>343DepScanFile::create(EntryRef Entry) {344assert(!Entry.isError() && "error");345346if (Entry.isDirectory())347return std::make_error_code(std::errc::is_a_directory);348349auto Result = std::make_unique<DepScanFile>(350llvm::MemoryBuffer::getMemBuffer(Entry.getContents(),351Entry.getStatus().getName(),352/*RequiresNullTerminator=*/false),353Entry.getStatus());354355return llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>(356std::unique_ptr<llvm::vfs::File>(std::move(Result)));357}358359llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>>360DependencyScanningWorkerFilesystem::openFileForRead(const Twine &Path) {361SmallString<256> OwnedFilename;362StringRef Filename = Path.toStringRef(OwnedFilename);363364if (Filename.ends_with(".pcm"))365return getUnderlyingFS().openFileForRead(Path);366367llvm::ErrorOr<EntryRef> Result = getOrCreateFileSystemEntry(Filename);368if (!Result)369return Result.getError();370return DepScanFile::create(Result.get());371}372373std::error_code374DependencyScanningWorkerFilesystem::getRealPath(const Twine &Path,375SmallVectorImpl<char> &Output) {376SmallString<256> OwnedFilename;377StringRef OriginalFilename = Path.toStringRef(OwnedFilename);378379SmallString<256> PathBuf;380auto FilenameForLookup = tryGetFilenameForLookup(OriginalFilename, PathBuf);381if (!FilenameForLookup)382return FilenameForLookup.getError();383384auto HandleCachedRealPath =385[&Output](const CachedRealPath &RealPath) -> std::error_code {386if (!RealPath)387return RealPath.getError();388Output.assign(RealPath->begin(), RealPath->end());389return {};390};391392// If we already have the result in local cache, no work required.393if (const auto *RealPath =394LocalCache.findRealPathByFilename(*FilenameForLookup))395return HandleCachedRealPath(*RealPath);396397// If we have the result in the shared cache, cache it locally.398auto &Shard = SharedCache.getShardForFilename(*FilenameForLookup);399if (const auto *ShardRealPath =400Shard.findRealPathByFilename(*FilenameForLookup)) {401const auto &RealPath = LocalCache.insertRealPathForFilename(402*FilenameForLookup, *ShardRealPath);403return HandleCachedRealPath(RealPath);404}405406// If we don't know the real path, compute it...407std::error_code EC = getUnderlyingFS().getRealPath(OriginalFilename, Output);408llvm::ErrorOr<llvm::StringRef> ComputedRealPath = EC;409if (!EC)410ComputedRealPath = StringRef{Output.data(), Output.size()};411412// ...and try to write it into the shared cache. In case some other thread won413// this race and already wrote its own result there, just adopt it. Write414// whatever is in the shared cache into the local one.415const auto &RealPath = Shard.getOrEmplaceRealPathForFilename(416*FilenameForLookup, ComputedRealPath);417return HandleCachedRealPath(418LocalCache.insertRealPathForFilename(*FilenameForLookup, RealPath));419}420421std::error_code DependencyScanningWorkerFilesystem::setCurrentWorkingDirectory(422const Twine &Path) {423std::error_code EC = ProxyFileSystem::setCurrentWorkingDirectory(Path);424updateWorkingDirForCacheLookup();425return EC;426}427428void DependencyScanningWorkerFilesystem::updateWorkingDirForCacheLookup() {429llvm::ErrorOr<std::string> CWD =430getUnderlyingFS().getCurrentWorkingDirectory();431if (!CWD) {432WorkingDirForCacheLookup = CWD.getError();433} else if (!llvm::sys::path::is_absolute_gnu(*CWD)) {434WorkingDirForCacheLookup = llvm::errc::invalid_argument;435} else {436WorkingDirForCacheLookup = *CWD;437}438assert(!WorkingDirForCacheLookup ||439llvm::sys::path::is_absolute_gnu(*WorkingDirForCacheLookup));440}441442llvm::ErrorOr<StringRef>443DependencyScanningWorkerFilesystem::tryGetFilenameForLookup(444StringRef OriginalFilename, llvm::SmallVectorImpl<char> &PathBuf) const {445StringRef FilenameForLookup;446if (llvm::sys::path::is_absolute_gnu(OriginalFilename)) {447FilenameForLookup = OriginalFilename;448} else if (!WorkingDirForCacheLookup) {449return WorkingDirForCacheLookup.getError();450} else {451StringRef RelFilename = OriginalFilename;452RelFilename.consume_front("./");453PathBuf.assign(WorkingDirForCacheLookup->begin(),454WorkingDirForCacheLookup->end());455llvm::sys::path::append(PathBuf, RelFilename);456FilenameForLookup = StringRef{PathBuf.begin(), PathBuf.size()};457}458assert(llvm::sys::path::is_absolute_gnu(FilenameForLookup));459return FilenameForLookup;460}461462const char DependencyScanningWorkerFilesystem::ID = 0;463464465