njames93 updated this revision to Diff 323378. njames93 added a comment. Rebase
Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D94554/new/ https://reviews.llvm.org/D94554 Files: clang-tools-extra/clangd/ClangdLSPServer.cpp clang-tools-extra/clangd/ClangdServer.cpp clang-tools-extra/clangd/ClangdServer.h clang-tools-extra/clangd/DraftStore.cpp clang-tools-extra/clangd/DraftStore.h clang-tools-extra/clangd/unittests/DraftStoreTests.cpp
Index: clang-tools-extra/clangd/unittests/DraftStoreTests.cpp =================================================================== --- clang-tools-extra/clangd/unittests/DraftStoreTests.cpp +++ clang-tools-extra/clangd/unittests/DraftStoreTests.cpp @@ -52,8 +52,8 @@ llvm::Expected<DraftStore::Draft> Result = DS.updateDraft(Path, llvm::None, {Event}); ASSERT_TRUE(!!Result); - EXPECT_EQ(Result->Contents, SrcAfter.code()); - EXPECT_EQ(DS.getDraft(Path)->Contents, SrcAfter.code()); + EXPECT_EQ(*Result->Contents, SrcAfter.code()); + EXPECT_EQ(*DS.getDraft(Path)->Contents, SrcAfter.code()); EXPECT_EQ(Result->Version, static_cast<int64_t>(i)); } } @@ -84,8 +84,8 @@ DS.updateDraft(Path, llvm::None, Changes); ASSERT_TRUE(!!Result) << llvm::toString(Result.takeError()); - EXPECT_EQ(Result->Contents, FinalSrc.code()); - EXPECT_EQ(DS.getDraft(Path)->Contents, FinalSrc.code()); + EXPECT_EQ(*Result->Contents, FinalSrc.code()); + EXPECT_EQ(*DS.getDraft(Path)->Contents, FinalSrc.code()); EXPECT_EQ(Result->Version, 1); } @@ -345,7 +345,7 @@ Optional<DraftStore::Draft> Contents = DS.getDraft(File); EXPECT_TRUE(Contents); - EXPECT_EQ(Contents->Contents, OriginalContents); + EXPECT_EQ(*Contents->Contents, OriginalContents); EXPECT_EQ(Contents->Version, 0); } Index: clang-tools-extra/clangd/DraftStore.h =================================================================== --- clang-tools-extra/clangd/DraftStore.h +++ clang-tools-extra/clangd/DraftStore.h @@ -11,6 +11,7 @@ #include "Protocol.h" #include "support/Path.h" +#include "support/ThreadsafeFS.h" #include "clang/Basic/LLVM.h" #include "llvm/ADT/StringMap.h" #include <mutex> @@ -20,6 +21,20 @@ namespace clang { namespace clangd { +/// A Reference counted string that is used to hold the contents of open files. +class RefCntString : public llvm::ThreadSafeRefCountedBase<RefCntString> { +public: + RefCntString(std::string Str) : Data(std::move(Str)) {} + + StringRef str() const { return Data; } + + operator const std::string &() const { return Data; } + operator StringRef() const { return str(); } + +private: + std::string Data; +}; + /// A thread-safe container for files opened in a workspace, addressed by /// filenames. The contents are owned by the DraftStore. This class supports /// both whole and incremental updates of the documents. @@ -28,10 +43,14 @@ class DraftStore { public: struct Draft { - std::string Contents; + llvm::IntrusiveRefCntPtr<RefCntString> Contents; int64_t Version = -1; }; + DraftStore(const ThreadsafeFS &BaseFS); + + DraftStore() = default; + /// \return Contents of the stored document. /// For untracked files, a llvm::None is returned. llvm::Optional<Draft> getDraft(PathRef File) const; @@ -59,9 +78,20 @@ /// Remove the draft from the store. void removeDraft(PathRef File); + const ThreadsafeFS &getDraftFS() const { + assert(DraftFS && "No draft fs has been set up"); + return *DraftFS; + } + private: + struct DraftAndTime { + Draft Draft; + llvm::sys::TimePoint<> MTime; + }; + class DraftstoreFS; + std::unique_ptr<ThreadsafeFS> DraftFS; mutable std::mutex Mutex; - llvm::StringMap<Draft> Drafts; + llvm::StringMap<DraftAndTime> Drafts; }; } // namespace clangd Index: clang-tools-extra/clangd/DraftStore.cpp =================================================================== --- clang-tools-extra/clangd/DraftStore.cpp +++ clang-tools-extra/clangd/DraftStore.cpp @@ -9,7 +9,9 @@ #include "DraftStore.h" #include "SourceCode.h" #include "support/Logger.h" +#include "llvm/Support/Chrono.h" #include "llvm/Support/Errc.h" +#include <cstdint> namespace clang { namespace clangd { @@ -21,7 +23,7 @@ if (It == Drafts.end()) return None; - return It->second; + return It->second.Draft; } std::vector<Path> DraftStore::getActiveFiles() const { @@ -47,14 +49,19 @@ } } +static void updateTime(llvm::sys::TimePoint<> &Time) { + Time = llvm::sys::TimePoint<>::clock::now(); +} + int64_t DraftStore::addDraft(PathRef File, llvm::Optional<int64_t> Version, llvm::StringRef Contents) { std::lock_guard<std::mutex> Lock(Mutex); - Draft &D = Drafts[File]; - updateVersion(D, Version); - D.Contents = Contents.str(); - return D.Version; + DraftAndTime &D = Drafts[File]; + updateVersion(D.Draft, Version); + updateTime(D.MTime); + D.Draft.Contents = llvm::makeIntrusiveRefCnt<RefCntString>(Contents.str()); + return D.Draft.Version; } llvm::Expected<DraftStore::Draft> DraftStore::updateDraft( @@ -68,8 +75,8 @@ "Trying to do incremental update on non-added document: {0}", File); } - Draft &D = EntryIt->second; - std::string Contents = EntryIt->second.Contents; + DraftAndTime &D = EntryIt->second; + std::string Contents = *D.Draft.Contents; for (const TextDocumentContentChangeEvent &Change : Changes) { if (!Change.range) { @@ -120,9 +127,11 @@ Contents = std::move(NewContents); } - updateVersion(D, Version); - D.Contents = std::move(Contents); - return D; + updateVersion(D.Draft, Version); + updateTime(D.MTime); + D.Draft.Contents = + llvm::makeIntrusiveRefCnt<RefCntString>(std::move(Contents)); + return D.Draft; } void DraftStore::removeDraft(PathRef File) { @@ -131,5 +140,175 @@ Drafts.erase(File); } +namespace { + +class RefCntStringMemBuffer : public llvm::MemoryBuffer { +public: + static std::unique_ptr<RefCntStringMemBuffer> + create(IntrusiveRefCntPtr<RefCntString> Data, StringRef Name) { + // Allocate space for the FileContentMemBuffer and its name with null + // terminator. + static_assert(alignof(RefCntStringMemBuffer) <= sizeof(void *), + "Alignment requirements can't be greater than pointer"); + auto MemSize = sizeof(RefCntStringMemBuffer) + Name.size() + 1; + return std::unique_ptr<RefCntStringMemBuffer>(new (operator new( + MemSize)) RefCntStringMemBuffer(std::move(Data), Name)); + } + + BufferKind getBufferKind() const override { + return MemoryBuffer::MemoryBuffer_Malloc; + } + + StringRef getBufferIdentifier() const override { + return StringRef(nameBegin(), NameSize); + } + +private: + RefCntStringMemBuffer(IntrusiveRefCntPtr<RefCntString> Data, StringRef Name) + : BufferContents(std::move(Data)), NameSize(Name.size()) { + MemoryBuffer::init(BufferContents->str().begin(), + BufferContents->str().end(), true); + memcpy(nameBegin(), Name.begin(), Name.size()); + nameBegin()[Name.size()] = '\0'; + } + + char *nameBegin() { return (char *)&this[1]; } + const char *nameBegin() const { return (const char *)&this[1]; } + + IntrusiveRefCntPtr<RefCntString> BufferContents; + size_t NameSize; +}; + +class DraftStoreFile : public llvm::vfs::File { +public: + struct FileData { + IntrusiveRefCntPtr<RefCntString> Contents; + llvm::sys::fs::UniqueID UID; + llvm::sys::TimePoint<> MTime; + }; + DraftStoreFile(std::string RequestedName, FileData Data) + : RequestedName(std::move(RequestedName)), Data(std::move(Data)) {} + + llvm::ErrorOr<llvm::vfs::Status> status() override { + return llvm::vfs::Status( + RequestedName, Data.UID, Data.MTime, 0, 0, Data.Contents->str().size(), + llvm::sys::fs::file_type::regular_file, llvm::sys::fs::all_all); + } + + llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> + getBuffer(const Twine &Name, int64_t FileSize, bool RequiresNullTerminator, + bool IsVolatile) override { + return RefCntStringMemBuffer::create(Data.Contents, RequestedName); + } + + std::error_code close() override { return {}; } + + ~DraftStoreFile() override { close(); } + +private: + std::string RequestedName; + FileData Data; +}; + +class DraftStoreFileSystem : public llvm::vfs::FileSystem { +public: + DraftStoreFileSystem(llvm::StringMap<DraftStoreFile::FileData> Contents) + : Contents(std::move(Contents)) {} + + llvm::ErrorOr<llvm::vfs::Status> status(const Twine &Path) override { + SmallString<256> PathStorage; + Path.toVector(PathStorage); + auto EC = makeAbsolute(PathStorage); + assert(!EC); + (void)EC; + auto Iter = Contents.find(PathStorage); + if (Iter == Contents.end()) + return llvm::errc::no_such_file_or_directory; + return DraftStoreFile(std::string(PathStorage), Iter->getValue()).status(); + } + + llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>> + openFileForRead(const Twine &Path) override { + SmallString<256> PathStorage; + Path.toVector(PathStorage); + auto EC = makeAbsolute(PathStorage); + assert(!EC); + (void)EC; + auto Iter = Contents.find(PathStorage); + if (Iter == Contents.end()) + return llvm::errc::no_such_file_or_directory; + return std::make_unique<DraftStoreFile>(std::string(PathStorage), + Iter->getValue()); + } + + llvm::ErrorOr<std::string> getCurrentWorkingDirectory() const override { + return WorkingDirectory; + } + + std::error_code setCurrentWorkingDirectory(const Twine &P) override { + SmallString<128> Path; + P.toVector(Path); + + // Fix up relative paths. This just prepends the current working + // directory. + std::error_code EC = makeAbsolute(Path); + assert(!EC); + (void)EC; + + llvm::sys::path::remove_dots(Path, /*remove_dot_dot=*/true); + + if (!Path.empty()) + WorkingDirectory = std::string(Path); + return {}; + } + llvm::vfs::directory_iterator dir_begin(const Twine &Dir, + std::error_code &EC) override { + EC = llvm::errc::no_such_file_or_directory; + return llvm::vfs::directory_iterator(); + } + +private: + std::string WorkingDirectory; + llvm::StringMap<DraftStoreFile::FileData> Contents; +}; + +} // namespace + +class DraftStore::DraftstoreFS : public ThreadsafeFS { +public: + DraftstoreFS(const ThreadsafeFS &Base, const DraftStore &DS) + : Base(Base), DS(DS) {} + + llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> viewImpl() const override { + auto BaseView = Base.view(llvm::None); + llvm::StringMap<DraftStoreFile::FileData> Contents; + std::lock_guard<std::mutex> Guard(DS.Mutex); + for (const auto &KV : DS.Drafts) { + // Query the base filesystem for file uniqueids. + auto BaseStatus = BaseView->status(KV.getKey()); + if (!BaseStatus) { + elog("Couldn't find file {0} when building DirtyFS", KV.getKey()); + continue; + } + // log("Adding dirty file {0} to dirty buffer", KV.getKey()); + Contents.insert(std::make_pair( + KV.getKey(), DraftStoreFile::FileData{KV.getValue().Draft.Contents, + BaseStatus->getUniqueID(), + KV.getValue().MTime})); + } + auto OFS = llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>( + std::move(BaseView)); + OFS->pushOverlay( + llvm::makeIntrusiveRefCnt<DraftStoreFileSystem>(std::move(Contents))); + return OFS; + } + +private: + const ThreadsafeFS &Base; + const DraftStore &DS; +}; + +DraftStore::DraftStore(const ThreadsafeFS &BaseFS) + : DraftFS(std::make_unique<DraftstoreFS>(BaseFS, *this)) {} } // namespace clangd } // namespace clang Index: clang-tools-extra/clangd/ClangdServer.h =================================================================== --- clang-tools-extra/clangd/ClangdServer.h +++ clang-tools-extra/clangd/ClangdServer.h @@ -156,7 +156,12 @@ /// those arguments for subsequent reparses. However, ClangdServer will check /// if compilation arguments changed on calls to forceReparse(). ClangdServer(const GlobalCompilationDatabase &CDB, const ThreadsafeFS &TFS, - const Options &Opts, Callbacks *Callbacks = nullptr); + const ThreadsafeFS &DirtyFS, const Options &Opts, + Callbacks *Callbacks = nullptr); + + ClangdServer(const GlobalCompilationDatabase &CDB, const ThreadsafeFS &TFS, + const Options &Opts, Callbacks *Callbacks = nullptr) + : ClangdServer(CDB, TFS, TFS, Opts, Callbacks) {} /// Add a \p File to the list of tracked C++ files or update the contents if /// \p File is already tracked. Also schedules parsing of the AST for it on a @@ -336,6 +341,8 @@ const GlobalCompilationDatabase &CDB; const ThreadsafeFS &TFS; + /// A File system that overlays open documents over the underlying filesystem. + const ThreadsafeFS &DirtyFS; Path ResourceDir; // The index used to look up symbols. This could be: Index: clang-tools-extra/clangd/ClangdServer.cpp =================================================================== --- clang-tools-extra/clangd/ClangdServer.cpp +++ clang-tools-extra/clangd/ClangdServer.cpp @@ -125,9 +125,9 @@ } ClangdServer::ClangdServer(const GlobalCompilationDatabase &CDB, - const ThreadsafeFS &TFS, const Options &Opts, - Callbacks *Callbacks) - : CDB(CDB), TFS(TFS), + const ThreadsafeFS &TFS, const ThreadsafeFS &DirtyFS, + const Options &Opts, Callbacks *Callbacks) + : CDB(CDB), TFS(TFS), DirtyFS(DirtyFS), DynamicIdx(Opts.BuildDynamicSymbolIndex ? new FileIndex() : nullptr), ClangTidyProvider(Opts.ClangTidyProvider), WorkspaceRoot(Opts.WorkspaceRoot), Index: clang-tools-extra/clangd/ClangdLSPServer.cpp =================================================================== --- clang-tools-extra/clangd/ClangdLSPServer.cpp +++ clang-tools-extra/clangd/ClangdLSPServer.cpp @@ -131,7 +131,7 @@ // If the file is open in user's editor, make sure the version we // saw and current version are compatible as this is the text that // will be replaced by editors. - if (!It.second.canApplyTo(Draft->Contents)) { + if (!It.second.canApplyTo(*Draft->Contents)) { ++InvalidFileCount; LastInvalidFile = It.first(); } @@ -539,7 +539,7 @@ llvm::Optional<WithContextValue> WithOffsetEncoding; if (Opts.Encoding) WithOffsetEncoding.emplace(kCurrentOffsetEncoding, *Opts.Encoding); - Server.emplace(*CDB, TFS, Opts, + Server.emplace(*CDB, TFS, DraftMgr.getDraftFS(), Opts, static_cast<ClangdServer::Callbacks *>(this)); } applyConfiguration(Params.initializationOptions.ConfigSettings); @@ -711,7 +711,7 @@ return; } - Server->addDocument(File, Draft->Contents, encodeVersion(Draft->Version), + Server->addDocument(File, *Draft->Contents, encodeVersion(Draft->Version), WantDiags, Params.forceRebuild); } @@ -902,7 +902,7 @@ "onDocumentOnTypeFormatting called for non-added file", ErrorCode::InvalidParams)); - Server->formatOnType(File, Code->Contents, Params.position, Params.ch, + Server->formatOnType(File, *Code->Contents, Params.position, Params.ch, std::move(Reply)); } @@ -917,11 +917,11 @@ ErrorCode::InvalidParams)); Server->formatRange( - File, Code->Contents, Params.range, + File, *Code->Contents, Params.range, [Code = Code->Contents, Reply = std::move(Reply)]( llvm::Expected<tooling::Replacements> Result) mutable { if (Result) - Reply(replacementsToEdits(Code, Result.get())); + Reply(replacementsToEdits(*Code, Result.get())); else Reply(Result.takeError()); }); @@ -937,11 +937,11 @@ "onDocumentFormatting called for non-added file", ErrorCode::InvalidParams)); - Server->formatFile(File, Code->Contents, + Server->formatFile(File, *Code->Contents, [Code = Code->Contents, Reply = std::move(Reply)]( llvm::Expected<tooling::Replacements> Result) mutable { if (Result) - Reply(replacementsToEdits(Code, Result.get())); + Reply(replacementsToEdits(*Code, Result.get())); else Reply(Result.takeError()); }); @@ -1484,7 +1484,8 @@ BackgroundContext(Context::current().clone()), Transp(Transp), MsgHandler(new MessageHandler(*this)), TFS(TFS), SupportedSymbolKinds(defaultSymbolKinds()), - SupportedCompletionItemKinds(defaultCompletionItemKinds()), Opts(Opts) { + SupportedCompletionItemKinds(defaultCompletionItemKinds()), DraftMgr(TFS), + Opts(Opts) { if (Opts.ConfigProvider) { assert(!Opts.ContextProvider && "Only one of ConfigProvider and ContextProvider allowed!"); @@ -1591,14 +1592,14 @@ auto Code = DraftMgr.getDraft(Params.textDocument.uri.file()); if (!Code) return true; // completion code will log the error for untracked doc. - auto Offset = positionToOffset(Code->Contents, Params.position, + auto Offset = positionToOffset(*Code->Contents, Params.position, /*AllowColumnsBeyondLineLength=*/false); if (!Offset) { vlog("could not convert position '{0}' to offset for file '{1}'", Params.position, Params.textDocument.uri.file()); return true; } - return allowImplicitCompletion(Code->Contents, *Offset); + return allowImplicitCompletion(*Code->Contents, *Offset); } void ClangdLSPServer::onDiagnosticsReady(PathRef File, llvm::StringRef Version, @@ -1718,7 +1719,7 @@ for (const Path &FilePath : DraftMgr.getActiveFiles()) if (Filter(FilePath)) if (auto Draft = DraftMgr.getDraft(FilePath)) // else disappeared in race? - Server->addDocument(FilePath, std::move(Draft->Contents), + Server->addDocument(FilePath, *Draft->Contents, encodeVersion(Draft->Version), WantDiagnostics::Auto); }
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits