//===--- Preamble.cpp - Reusing expensive parts of the AST ----------------===// // // 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 // //===----------------------------------------------------------------------===// #include "Preamble.h" #include "Logger.h" #include "Trace.h" #include "clang/Basic/SourceLocation.h" #include "clang/Lex/PPCallbacks.h" #include "clang/Lex/PreprocessorOptions.h" namespace clang { namespace clangd { namespace { bool compileCommandsAreEqual(const tooling::CompileCommand &LHS, const tooling::CompileCommand &RHS) { // We don't check for Output, it should not matter to clangd. return LHS.Directory == RHS.Directory && LHS.Filename == RHS.Filename && llvm::makeArrayRef(LHS.CommandLine).equals(RHS.CommandLine); } class CppFilePreambleCallbacks : public PreambleCallbacks { public: CppFilePreambleCallbacks(PathRef File, PreambleParsedCallback ParsedCallback) : File(File), ParsedCallback(ParsedCallback) {} IncludeStructure takeIncludes() { return std::move(Includes); } MainFileMacros takeMacros() { return std::move(Macros); } CanonicalIncludes takeCanonicalIncludes() { return std::move(CanonIncludes); } void AfterExecute(CompilerInstance &CI) override { if (!ParsedCallback) return; trace::Span Tracer("Running PreambleCallback"); ParsedCallback(CI.getASTContext(), CI.getPreprocessorPtr(), CanonIncludes); } void BeforeExecute(CompilerInstance &CI) override { CanonIncludes.addSystemHeadersMapping(CI.getLangOpts()); LangOpts = &CI.getLangOpts(); SourceMgr = &CI.getSourceManager(); } std::unique_ptr createPPCallbacks() override { assert(SourceMgr && LangOpts && "SourceMgr and LangOpts must be set at this point"); return std::make_unique( collectIncludeStructureCallback(*SourceMgr, &Includes), std::make_unique(*SourceMgr, *LangOpts, Macros)); } CommentHandler *getCommentHandler() override { IWYUHandler = collectIWYUHeaderMaps(&CanonIncludes); return IWYUHandler.get(); } private: PathRef File; PreambleParsedCallback ParsedCallback; IncludeStructure Includes; CanonicalIncludes CanonIncludes; MainFileMacros Macros; std::unique_ptr IWYUHandler = nullptr; const clang::LangOptions *LangOpts = nullptr; const SourceManager *SourceMgr = nullptr; }; } // namespace PreambleData::PreambleData(PrecompiledPreamble Preamble, std::vector Diags, IncludeStructure Includes, MainFileMacros Macros, std::unique_ptr StatCache, CanonicalIncludes CanonIncludes) : Preamble(std::move(Preamble)), Diags(std::move(Diags)), Includes(std::move(Includes)), Macros(std::move(Macros)), StatCache(std::move(StatCache)), CanonIncludes(std::move(CanonIncludes)) { } std::shared_ptr buildPreamble(PathRef FileName, CompilerInvocation &CI, std::shared_ptr OldPreamble, const tooling::CompileCommand &OldCompileCommand, const ParseInputs &Inputs, bool StoreInMemory, PreambleParsedCallback PreambleCallback) { // Note that we don't need to copy the input contents, preamble can live // without those. auto ContentsBuffer = llvm::MemoryBuffer::getMemBuffer(Inputs.Contents, FileName); auto Bounds = ComputePreambleBounds(*CI.getLangOpts(), ContentsBuffer.get(), 0); if (OldPreamble && compileCommandsAreEqual(Inputs.CompileCommand, OldCompileCommand) && OldPreamble->Preamble.CanReuse(CI, ContentsBuffer.get(), Bounds, Inputs.FS.get())) { vlog("Reusing preamble for {0}", FileName); return OldPreamble; } vlog(OldPreamble ? "Rebuilding invalidated preamble for {0}" : "Building first preamble for {0}", FileName); trace::Span Tracer("BuildPreamble"); SPAN_ATTACH(Tracer, "File", FileName); StoreDiags PreambleDiagnostics; llvm::IntrusiveRefCntPtr PreambleDiagsEngine = CompilerInstance::createDiagnostics(&CI.getDiagnosticOpts(), &PreambleDiagnostics, false); // Skip function bodies when building the preamble to speed up building // the preamble and make it smaller. assert(!CI.getFrontendOpts().SkipFunctionBodies); CI.getFrontendOpts().SkipFunctionBodies = true; // We don't want to write comment locations into PCH. They are racy and slow // to read back. We rely on dynamic index for the comments instead. CI.getPreprocessorOpts().WriteCommentListToPCH = false; CppFilePreambleCallbacks SerializedDeclsCollector(FileName, PreambleCallback); if (Inputs.FS->setCurrentWorkingDirectory(Inputs.CompileCommand.Directory)) { log("Couldn't set working directory when building the preamble."); // We proceed anyway, our lit-tests rely on results for non-existing working // dirs. } llvm::SmallString<32> AbsFileName(FileName); Inputs.FS->makeAbsolute(AbsFileName); auto StatCache = std::make_unique(AbsFileName); auto BuiltPreamble = PrecompiledPreamble::Build( CI, ContentsBuffer.get(), Bounds, *PreambleDiagsEngine, StatCache->getProducingFS(Inputs.FS), std::make_shared(), StoreInMemory, SerializedDeclsCollector); // When building the AST for the main file, we do want the function // bodies. CI.getFrontendOpts().SkipFunctionBodies = false; if (BuiltPreamble) { vlog("Built preamble of size {0} for file {1}", BuiltPreamble->getSize(), FileName); std::vector Diags = PreambleDiagnostics.take(); return std::make_shared( std::move(*BuiltPreamble), std::move(Diags), SerializedDeclsCollector.takeIncludes(), SerializedDeclsCollector.takeMacros(), std::move(StatCache), SerializedDeclsCollector.takeCanonicalIncludes()); } else { elog("Could not build a preamble for file {0}", FileName); return nullptr; } } } // namespace clangd } // namespace clang