[clang] 2214b90 - [clangd] Make signatureHelp work with stale preambles

Mikael Holmén via cfe-commits cfe-commits at lists.llvm.org
Thu Apr 23 23:11:51 PDT 2020


On Thu, 2020-04-23 at 17:46 +0300, Kadir Çetinkaya wrote:
> Thanks Mikael, sent out an ugly fix
> at 89cb5d558895706e053bc3af972aa5b15aa82863 to get sanitizer build
> bots running.
> 

Nice, thanks! Now our buildbots went silent again.

/Mikael

> Will change the testing scheme for a proper fix.
> 
> On Thu, Apr 23, 2020 at 4:53 PM Mikael Holmén <
> mikael.holmen at ericsson.com> wrote:
> > Hi Kadir,
> > 
> > I start seeing some sanitizer complaints with this commit for the
> > following two testcases:
> >   ./ClangdTests/PreamblePatchTest.ContainsNewIncludes
> >   ./ClangdTests/PreamblePatchTest.IncludeParsing
> > 
> > The complaints look like this:
> > 
> > =================================================================
> > ==75126==ERROR: LeakSanitizer: detected memory leaks
> > 
> > Direct leak of 369 byte(s) in 4 object(s) allocated from:
> >     #0 0x5bbe40 in operator new(unsigned long, std::nothrow_t
> > const&)
> > /repo/uabkaka/tmp/llvm/projects/compiler-
> > rt/lib/asan/asan_new_delete.cc:112
> >     #1 0x149a3e9 in
> > llvm::WritableMemoryBuffer::getNewUninitMemBuffer(unsigned long,
> > llvm::Twine const&) /repo/bbiswjenk/fem023-
> > eiffel003/workspace/llvm/llvm-master-sanitize-asan/llvm/build-all-
> > asan/../lib/Support/MemoryBuffer.cpp:298:34
> >     #2 0x1498db3 in getMemBufferCopyImpl /repo/bbiswjenk/fem023-
> > eiffel003/workspace/llvm/llvm-master-sanitize-asan/llvm/build-all-
> > asan/../lib/Support/MemoryBuffer.cpp:127:14
> >     #3 0x1498db3 in
> > llvm::MemoryBuffer::getMemBufferCopy(llvm::StringRef, llvm::Twine
> > const&) /repo/bbiswjenk/fem023-eiffel003/workspace/llvm/llvm-
> > master-
> > sanitize-asan/llvm/build-all-
> > asan/../lib/Support/MemoryBuffer.cpp:136
> >     #4 0x506012e in
> > clang::clangd::PreamblePatch::apply(clang::CompilerInvocation&)
> > const
> > /repo/bbiswjenk/fem023-eiffel003/workspace/llvm/llvm-master-
> > sanitize-
> > asan/llvm/build-all-asan/../../clang-tools-
> > extra/clangd/Preamble.cpp:337:7
> >     #5 0xf910e6 in clang::clangd::(anonymous
> > namespace)::PreamblePatchTest_IncludeParsing_Test::TestBody()
> > /repo/bbiswjenk/fem023-eiffel003/workspace/llvm/llvm-master-
> > sanitize-
> > asan/llvm/build-all-asan/../../clang-tools-
> > extra/clangd/unittests/PreambleTests.cpp:91:57
> >     #6 0x164e8e0 in
> > HandleExceptionsInMethodIfSupported<testing::Test,
> > void> /repo/bbiswjenk/fem023-eiffel003/workspace/llvm/llvm-master-
> > sanitize-asan/llvm/build-all-
> > asan/../utils/unittest/googletest/src/gtest.cc
> >     #7 0x164e8e0 in testing::Test::Run() /repo/bbiswjenk/fem023-
> > eiffel003/workspace/llvm/llvm-master-sanitize-asan/llvm/build-all-
> > asan/../utils/unittest/googletest/src/gtest.cc:2474
> >     #8 0x1651fc5 in testing::TestInfo::Run()
> > /repo/bbiswjenk/fem023-
> > eiffel003/workspace/llvm/llvm-master-sanitize-asan/llvm/build-all-
> > asan/../utils/unittest/googletest/src/gtest.cc:2656:11
> >     #9 0x1653440 in testing::TestCase::Run()
> > /repo/bbiswjenk/fem023-
> > eiffel003/workspace/llvm/llvm-master-sanitize-asan/llvm/build-all-
> > asan/../utils/unittest/googletest/src/gtest.cc:2774:28
> >     #10 0x1671e64 in testing::internal::UnitTestImpl::RunAllTests()
> > /repo/bbiswjenk/fem023-eiffel003/workspace/llvm/llvm-master-
> > sanitize-
> > asan/llvm/build-all-
> > asan/../utils/unittest/googletest/src/gtest.cc:4649:43
> >     #11 0x1671010 in
> > HandleExceptionsInMethodIfSupported<testing::internal::UnitTestImpl
> > ,
> > bool> /repo/bbiswjenk/fem023-eiffel003/workspace/llvm/llvm-master-
> > sanitize-asan/llvm/build-all-
> > asan/../utils/unittest/googletest/src/gtest.cc
> >     #12 0x1671010 in testing::UnitTest::Run()
> > /repo/bbiswjenk/fem023-
> > eiffel003/workspace/llvm/llvm-master-sanitize-asan/llvm/build-all-
> > asan/../utils/unittest/googletest/src/gtest.cc:4257
> >     #13 0x1633040 in RUN_ALL_TESTS /repo/bbiswjenk/fem023-
> > eiffel003/workspace/llvm/llvm-master-sanitize-asan/llvm/build-all-
> > asan/../utils/unittest/googletest/include/gtest/gtest.h:2233:46
> >     #14 0x1633040 in main /repo/bbiswjenk/fem023-
> > eiffel003/workspace/llvm/llvm-master-sanitize-asan/llvm/build-all-
> > asan/../utils/unittest/UnitTestMain/TestMain.cpp:50
> >     #15 0x7f7dfdfe6544 in __libc_start_main
> > (/lib64/libc.so.6+0x22544)
> > 
> > SUMMARY: AddressSanitizer: 369 byte(s) leaked in 4 allocation(s).
> > 
> > Regards,
> > Mikael
> > 
> > On Tue, 2020-04-21 at 01:34 -0700, Kadir Cetinkaya via cfe-commits
> > wrote:
> > > Author: Kadir Cetinkaya
> > > Date: 2020-04-21T10:27:26+02:00
> > > New Revision: 2214b9076f1d3a4784820c4479e2417685e5c980
> > > 
> > > URL: 
> > > 
> > https://github.com/llvm/llvm-project/commit/2214b9076f1d3a4784820c4479e2417685e5c980
> > > DIFF: 
> > > 
> > https://github.com/llvm/llvm-project/commit/2214b9076f1d3a4784820c4479e2417685e5c980.diff
> > > 
> > > LOG: [clangd] Make signatureHelp work with stale preambles
> > > 
> > > Summary:
> > > This is achieved by calculating newly added includes and
> > implicitly
> > > parsing them as if they were part of the main file.
> > > 
> > > This also gets rid of the need for consistent preamble reads.
> > > 
> > > Reviewers: sammccall
> > > 
> > > Subscribers: ilya-biryukov, javed.absar, MaskRay, jkorous,
> > mgrang,
> > > arphaman, jfb, usaxena95, cfe-commits
> > > 
> > > Tags: #clang
> > > 
> > > Differential Revision: https://reviews.llvm.org/D77392
> > > 
> > > Added: 
> > >     clang-tools-extra/clangd/unittests/PreambleTests.cpp
> > > 
> > > Modified: 
> > >     clang-tools-extra/clangd/ClangdServer.cpp
> > >     clang-tools-extra/clangd/CodeComplete.cpp
> > >     clang-tools-extra/clangd/Preamble.cpp
> > >     clang-tools-extra/clangd/Preamble.h
> > >     clang-tools-extra/clangd/TUScheduler.cpp
> > >     clang-tools-extra/clangd/TUScheduler.h
> > >     clang-tools-extra/clangd/unittests/CMakeLists.txt
> > >     clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
> > >     clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp
> > >     clang/include/clang/Basic/TokenKinds.h
> > >     clang/include/clang/Frontend/PrecompiledPreamble.h
> > > 
> > > Removed: 
> > >     
> > > 
> > > 
> > >
> > ###################################################################
> > ##
> > > ###########
> > > diff  --git a/clang-tools-extra/clangd/ClangdServer.cpp b/clang-
> > > tools-extra/clangd/ClangdServer.cpp
> > > index f82bfa4f2682..c5148f81f3df 100644
> > > --- a/clang-tools-extra/clangd/ClangdServer.cpp
> > > +++ b/clang-tools-extra/clangd/ClangdServer.cpp
> > > @@ -280,11 +280,8 @@ void ClangdServer::signatureHelp(PathRef
> > File,
> > > Position Pos,
> > >                               Pos, FS, Index));
> > >    };
> > >  
> > > -  // Unlike code completion, we wait for an up-to-date preamble
> > > here.
> > > -  // Signature help is often triggered after code completion. If
> > the
> > > code
> > > -  // completion inserted a header to make the symbol available,
> > then
> > > using
> > > -  // the old preamble would yield useless results.
> > > -  WorkScheduler.runWithPreamble("SignatureHelp", File,
> > > TUScheduler::Consistent,
> > > +  // Unlike code completion, we wait for a preamble here.
> > > +  WorkScheduler.runWithPreamble("SignatureHelp", File,
> > > TUScheduler::Stale,
> > >                                  std::move(Action));
> > >  }
> > >  
> > > 
> > > diff  --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-
> > > tools-extra/clangd/CodeComplete.cpp
> > > index 7dbb4f5b78a3..3fa5b53ebaad 100644
> > > --- a/clang-tools-extra/clangd/CodeComplete.cpp
> > > +++ b/clang-tools-extra/clangd/CodeComplete.cpp
> > > @@ -1023,6 +1023,7 @@ struct SemaCompleteInput {
> > >    PathRef FileName;
> > >    const tooling::CompileCommand &Command;
> > >    const PreambleData &Preamble;
> > > +  const PreamblePatch &PreamblePatch;
> > >    llvm::StringRef Contents;
> > >    size_t Offset;
> > >    llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS;
> > > @@ -1060,7 +1061,6 @@ bool
> > > semaCodeComplete(std::unique_ptr<CodeCompleteConsumer> Consumer,
> > >    ParseInput.CompileCommand = Input.Command;
> > >    ParseInput.FS = VFS;
> > >    ParseInput.Contents = std::string(Input.Contents);
> > > -  ParseInput.Opts = ParseOptions();
> > >  
> > >    IgnoreDiagnostics IgnoreDiags;
> > >    auto CI = buildCompilerInvocation(ParseInput, IgnoreDiags);
> > > @@ -1096,6 +1096,7 @@ bool
> > > semaCodeComplete(std::unique_ptr<CodeCompleteConsumer> Consumer,
> > >    PreambleBounds PreambleRegion =
> > >        ComputePreambleBounds(*CI->getLangOpts(),
> > > ContentsBuffer.get(), 0);
> > >    bool CompletingInPreamble = PreambleRegion.Size >
> > Input.Offset;
> > > +  Input.PreamblePatch.apply(*CI);
> > >    // NOTE: we must call BeginSourceFile after
> > > prepareCompilerInstance. Otherwise
> > >    // the remapped buffers do not get freed.
> > >    auto Clang = prepareCompilerInstance(
> > > @@ -1754,8 +1755,10 @@ codeComplete(PathRef FileName, const
> > > tooling::CompileCommand &Command,
> > >        SpecFuzzyFind, Opts);
> > >    return (!Preamble || Opts.RunParser ==
> > > CodeCompleteOptions::NeverParse)
> > >               ? std::move(Flow).runWithoutSema(Contents, *Offset,
> > > VFS)
> > > -             : std::move(Flow).run(
> > > -                   {FileName, Command, *Preamble, Contents,
> > *Offset,
> > > VFS});
> > > +             : std::move(Flow).run({FileName, Command,
> > *Preamble,
> > > +                                    // We want to serve code
> > > completions with
> > > +                                    // low latency, so don't
> > bother
> > > patching.
> > > +                                    PreamblePatch(), Contents,
> > > *Offset, VFS});
> > >  }
> > >  
> > >  SignatureHelp signatureHelp(PathRef FileName,
> > > @@ -1775,10 +1778,15 @@ SignatureHelp signatureHelp(PathRef
> > FileName,
> > >    Options.IncludeMacros = false;
> > >    Options.IncludeCodePatterns = false;
> > >    Options.IncludeBriefComments = false;
> > > -  IncludeStructure PreambleInclusions; // Unused for
> > signatureHelp
> > > +
> > > +  ParseInputs PI;
> > > +  PI.CompileCommand = Command;
> > > +  PI.Contents = Contents.str();
> > > +  PI.FS = std::move(VFS);
> > > +  auto PP = PreamblePatch::create(FileName, PI, Preamble);
> > >    semaCodeComplete(
> > >        std::make_unique<SignatureHelpCollector>(Options, Index,
> > > Result), Options,
> > > -      {FileName, Command, Preamble, Contents, *Offset,
> > > std::move(VFS)});
> > > +      {FileName, Command, Preamble, PP, Contents, *Offset,
> > > std::move(PI.FS)});
> > >    return Result;
> > >  }
> > >  
> > > 
> > > diff  --git a/clang-tools-extra/clangd/Preamble.cpp b/clang-
> > tools-
> > > extra/clangd/Preamble.cpp
> > > index 97cd22a5d1fc..8392748227b4 100644
> > > --- a/clang-tools-extra/clangd/Preamble.cpp
> > > +++ b/clang-tools-extra/clangd/Preamble.cpp
> > > @@ -8,11 +8,39 @@
> > >  
> > >  #include "Preamble.h"
> > >  #include "Compiler.h"
> > > +#include "Headers.h"
> > >  #include "Logger.h"
> > >  #include "Trace.h"
> > > +#include "clang/Basic/Diagnostic.h"
> > > +#include "clang/Basic/LangOptions.h"
> > >  #include "clang/Basic/SourceLocation.h"
> > > +#include "clang/Basic/TokenKinds.h"
> > > +#include "clang/Frontend/CompilerInvocation.h"
> > > +#include "clang/Frontend/FrontendActions.h"
> > > +#include "clang/Lex/Lexer.h"
> > >  #include "clang/Lex/PPCallbacks.h"
> > > +#include "clang/Lex/Preprocessor.h"
> > >  #include "clang/Lex/PreprocessorOptions.h"
> > > +#include "clang/Tooling/CompilationDatabase.h"
> > > +#include "llvm/ADT/ArrayRef.h"
> > > +#include "llvm/ADT/IntrusiveRefCntPtr.h"
> > > +#include "llvm/ADT/STLExtras.h"
> > > +#include "llvm/ADT/SmallString.h"
> > > +#include "llvm/ADT/StringRef.h"
> > > +#include "llvm/ADT/StringSet.h"
> > > +#include "llvm/Support/Error.h"
> > > +#include "llvm/Support/ErrorHandling.h"
> > > +#include "llvm/Support/FormatVariadic.h"
> > > +#include "llvm/Support/MemoryBuffer.h"
> > > +#include "llvm/Support/Path.h"
> > > +#include "llvm/Support/VirtualFileSystem.h"
> > > +#include "llvm/Support/raw_ostream.h"
> > > +#include <iterator>
> > > +#include <memory>
> > > +#include <string>
> > > +#include <system_error>
> > > +#include <utility>
> > > +#include <vector>
> > >  
> > >  namespace clang {
> > >  namespace clangd {
> > > @@ -74,6 +102,81 @@ class CppFilePreambleCallbacks : public
> > > PreambleCallbacks {
> > >    const SourceManager *SourceMgr = nullptr;
> > >  };
> > >  
> > > +// Runs preprocessor over preamble section.
> > > +class PreambleOnlyAction : public PreprocessorFrontendAction {
> > > +protected:
> > > +  void ExecuteAction() override {
> > > +    Preprocessor &PP = getCompilerInstance().getPreprocessor();
> > > +    auto &SM = PP.getSourceManager();
> > > +    PP.EnterMainSourceFile();
> > > +    auto Bounds =
> > > ComputePreambleBounds(getCompilerInstance().getLangOpts(),
> > > +                                       
> > SM.getBuffer(SM.getMainFileI
> > > D()), 0);
> > > +    Token Tok;
> > > +    do {
> > > +      PP.Lex(Tok);
> > > +      assert(SM.isInMainFile(Tok.getLocation()));
> > > +    } while (Tok.isNot(tok::eof) &&
> > > +             SM.getDecomposedLoc(Tok.getLocation()).second <
> > > Bounds.Size);
> > > +  }
> > > +};
> > > +
> > > +/// Gets the includes in the preamble section of the file by
> > running
> > > +/// preprocessor over \p Contents. Returned includes do not
> > contain
> > > resolved
> > > +/// paths. \p VFS and \p Cmd is used to build the compiler
> > > invocation, which
> > > +/// might stat/read files.
> > > +llvm::Expected<std::vector<Inclusion>>
> > > +scanPreambleIncludes(llvm::StringRef Contents,
> > > +                   
> >  llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem>
> > > VFS,
> > > +                     const tooling::CompileCommand &Cmd) {
> > > +  // Build and run Preprocessor over the preamble.
> > > +  ParseInputs PI;
> > > +  PI.Contents = Contents.str();
> > > +  PI.FS = std::move(VFS);
> > > +  PI.CompileCommand = Cmd;
> > > +  IgnoringDiagConsumer IgnoreDiags;
> > > +  auto CI = buildCompilerInvocation(PI, IgnoreDiags);
> > > +  if (!CI)
> > > +    return
> > llvm::createStringError(llvm::inconvertibleErrorCode(),
> > > +                                   "failed to create compiler
> > > invocation");
> > > +  CI->getDiagnosticOpts().IgnoreWarnings = true;
> > > +  auto ContentsBuffer =
> > llvm::MemoryBuffer::getMemBuffer(Contents);
> > > +  auto Clang = prepareCompilerInstance(
> > > +      std::move(CI), nullptr, std::move(ContentsBuffer),
> > > +      // Provide an empty FS to prevent preprocessor from
> > performing
> > > IO. This
> > > +      // also implies missing resolved paths for includes.
> > > +      new llvm::vfs::InMemoryFileSystem, IgnoreDiags);
> > > +  if (Clang->getFrontendOpts().Inputs.empty())
> > > +    return
> > llvm::createStringError(llvm::inconvertibleErrorCode(),
> > > +                                   "compiler instance had no
> > > inputs");
> > > +  // We are only interested in main file includes.
> > > +  Clang->getPreprocessorOpts().SingleFileParseMode = true;
> > > +  PreambleOnlyAction Action;
> > > +  if (!Action.BeginSourceFile(*Clang, Clang-
> > > >getFrontendOpts().Inputs[0]))
> > > +    return
> > llvm::createStringError(llvm::inconvertibleErrorCode(),
> > > +                                   "failed BeginSourceFile");
> > > +  Preprocessor &PP = Clang->getPreprocessor();
> > > +  IncludeStructure Includes;
> > > +  PP.addPPCallbacks(
> > > +      collectIncludeStructureCallback(Clang->getSourceManager(),
> > > &Includes));
> > > +  if (llvm::Error Err = Action.Execute())
> > > +    return std::move(Err);
> > > +  Action.EndSourceFile();
> > > +  return Includes.MainFileIncludes;
> > > +}
> > > +
> > > +const char *spellingForIncDirective(tok::PPKeywordKind
> > > IncludeDirective) {
> > > +  switch (IncludeDirective) {
> > > +  case tok::pp_include:
> > > +    return "include";
> > > +  case tok::pp_import:
> > > +    return "import";
> > > +  case tok::pp_include_next:
> > > +    return "include_next";
> > > +  default:
> > > +    break;
> > > +  }
> > > +  llvm_unreachable("not an include directive");
> > > +}
> > >  } // namespace
> > >  
> > >  PreambleData::PreambleData(const ParseInputs &Inputs,
> > > @@ -166,5 +269,78 @@ bool isPreambleCompatible(const PreambleData
> > > &Preamble,
> > >           Preamble.Preamble.CanReuse(CI, ContentsBuffer.get(),
> > > Bounds,
> > >                                      Inputs.FS.get());
> > >  }
> > > +
> > > +PreamblePatch PreamblePatch::create(llvm::StringRef FileName,
> > > +                                    const ParseInputs &Modified,
> > > +                                    const PreambleData
> > &Baseline) {
> > > +  // First scan the include directives in Baseline and Modified.
> > > These will be
> > > +  // used to figure out newly added directives in Modified.
> > Scanning
> > > can fail,
> > > +  // the code just bails out and creates an empty patch in such
> > > cases, as:
> > > +  // - If scanning for Baseline fails, no knowledge of existing
> > > includes hence
> > > +  //   patch will contain all the includes in Modified. Leading
> > to
> > > rebuild of
> > > +  //   whole preamble, which is terribly slow.
> > > +  // - If scanning for Modified fails, cannot figure out newly
> > added
> > > ones so
> > > +  //   there's nothing to do but generate an empty patch.
> > > +  auto BaselineIncludes = scanPreambleIncludes(
> > > +      // Contents needs to be null-terminated.
> > > +      Baseline.Preamble.getContents().str(),
> > > +      Baseline.StatCache->getConsumingFS(Modified.FS),
> > > Modified.CompileCommand);
> > > +  if (!BaselineIncludes) {
> > > +    elog("Failed to scan includes for baseline of {0}: {1}",
> > > FileName,
> > > +         BaselineIncludes.takeError());
> > > +    return {};
> > > +  }
> > > +  auto ModifiedIncludes = scanPreambleIncludes(
> > > +      Modified.Contents, Baseline.StatCache-
> > > >getConsumingFS(Modified.FS),
> > > +      Modified.CompileCommand);
> > > +  if (!ModifiedIncludes) {
> > > +    elog("Failed to scan includes for modified contents of {0}:
> > > {1}", FileName,
> > > +         ModifiedIncludes.takeError());
> > > +    return {};
> > > +  }
> > > +
> > > +  PreamblePatch PP;
> > > +  // This shouldn't coincide with any real file name.
> > > +  llvm::SmallString<128> PatchName;
> > > +  llvm::sys::path::append(PatchName,
> > > llvm::sys::path::parent_path(FileName),
> > > +                          "__preamble_patch__.h");
> > > +  PP.PatchFileName = PatchName.str().str();
> > > +
> > > +  // We are only interested in newly added includes, record the
> > ones
> > > in Baseline
> > > +  // for exclusion.
> > > +  llvm::DenseSet<std::pair<tok::PPKeywordKind, llvm::StringRef>>
> > > +      ExistingIncludes;
> > > +  for (const auto &Inc : *BaselineIncludes)
> > > +    ExistingIncludes.insert({Inc.Directive, Inc.Written});
> > > +  // Calculate extra includes that needs to be inserted.
> > > +  llvm::raw_string_ostream Patch(PP.PatchContents);
> > > +  for (const auto &Inc : *ModifiedIncludes) {
> > > +    if (ExistingIncludes.count({Inc.Directive, Inc.Written}))
> > > +      continue;
> > > +    Patch << llvm::formatv("#{0} {1}\n",
> > > spellingForIncDirective(Inc.Directive),
> > > +                           Inc.Written);
> > > +  }
> > > +  Patch.flush();
> > > +
> > > +  // FIXME: Handle more directives, e.g. define/undef.
> > > +  return PP;
> > > +}
> > > +
> > > +void PreamblePatch::apply(CompilerInvocation &CI) const {
> > > +  // No need to map an empty file.
> > > +  if (PatchContents.empty())
> > > +    return;
> > > +  auto &PPOpts = CI.getPreprocessorOpts();
> > > +  auto PatchBuffer =
> > > +      // we copy here to ensure contents are still valid if CI
> > > outlives the
> > > +      // PreamblePatch.
> > > +      llvm::MemoryBuffer::getMemBufferCopy(PatchContents,
> > > PatchFileName);
> > > +  // CI will take care of the lifetime of the buffer.
> > > +  PPOpts.addRemappedFile(PatchFileName, PatchBuffer.release());
> > > +  // The patch will be parsed after loading the preamble ast and
> > > before parsing
> > > +  // the main file.
> > > +  PPOpts.Includes.push_back(PatchFileName);
> > > +}
> > > +
> > >  } // namespace clangd
> > >  } // namespace clang
> > > 
> > > diff  --git a/clang-tools-extra/clangd/Preamble.h b/clang-tools-
> > > extra/clangd/Preamble.h
> > > index bd67610e0ad7..10c292a71f38 100644
> > > --- a/clang-tools-extra/clangd/Preamble.h
> > > +++ b/clang-tools-extra/clangd/Preamble.h
> > > @@ -32,6 +32,7 @@
> > >  #include "clang/Frontend/CompilerInvocation.h"
> > >  #include "clang/Frontend/PrecompiledPreamble.h"
> > >  #include "clang/Tooling/CompilationDatabase.h"
> > > +#include "llvm/ADT/StringRef.h"
> > >  
> > >  #include <memory>
> > >  #include <string>
> > > @@ -88,6 +89,31 @@ buildPreamble(PathRef FileName,
> > CompilerInvocation
> > > CI,
> > >  bool isPreambleCompatible(const PreambleData &Preamble,
> > >                            const ParseInputs &Inputs, PathRef
> > > FileName,
> > >                            const CompilerInvocation &CI);
> > > +
> > > +/// Stores information required to parse a TU using a (possibly
> > > stale) Baseline
> > > +/// preamble. Updates compiler invocation to approximately
> > reflect
> > > additions to
> > > +/// the preamble section of Modified contents, e.g. new include
> > > directives.
> > > +class PreamblePatch {
> > > +public:
> > > +  // With an empty patch, the preamble is used verbatim.
> > > +  PreamblePatch() = default;
> > > +  /// Builds a patch that contains new PP directives introduced
> > to
> > > the preamble
> > > +  /// section of \p Modified compared to \p Baseline.
> > > +  /// FIXME: This only handles include directives, we should at
> > > least handle
> > > +  /// define/undef.
> > > +  static PreamblePatch create(llvm::StringRef FileName,
> > > +                              const ParseInputs &Modified,
> > > +                              const PreambleData &Baseline);
> > > +  /// Adjusts CI (which compiles the modified inputs) to be used
> > > with the
> > > +  /// baseline preamble. This is done by inserting an artifical
> > > include to the
> > > +  /// \p CI that contains new directives calculated in create.
> > > +  void apply(CompilerInvocation &CI) const;
> > > +
> > > +private:
> > > +  std::string PatchContents;
> > > +  std::string PatchFileName;
> > > +};
> > > +
> > >  } // namespace clangd
> > >  } // namespace clang
> > >  
> > > 
> > > diff  --git a/clang-tools-extra/clangd/TUScheduler.cpp b/clang-
> > tools-
> > > extra/clangd/TUScheduler.cpp
> > > index 26adcfd2b8f2..2f2abb59ab3c 100644
> > > --- a/clang-tools-extra/clangd/TUScheduler.cpp
> > > +++ b/clang-tools-extra/clangd/TUScheduler.cpp
> > > @@ -866,36 +866,6 @@ ASTWorker::getPossiblyStalePreamble() const
> > {
> > >    return LatestPreamble;
> > >  }
> > >  
> > > -void ASTWorker::getCurrentPreamble(
> > > -    llvm::unique_function<void(std::shared_ptr<const
> > PreambleData>)>
> > > Callback) {
> > > -  // We could just call startTask() to throw the read on the
> > queue,
> > > knowing
> > > -  // it will run after any updates. But we know this task is
> > cheap,
> > > so to
> > > -  // improve latency we cheat: insert it on the queue after the
> > last
> > > update.
> > > -  std::unique_lock<std::mutex> Lock(Mutex);
> > > -  auto LastUpdate =
> > > -      std::find_if(Requests.rbegin(), Requests.rend(),
> > > -                   [](const Request &R) { return
> > > R.UpdateType.hasValue(); });
> > > -  // If there were no writes in the queue, and CurrentRequest is
> > not
> > > a write,
> > > -  // the preamble is ready now.
> > > -  if (LastUpdate == Requests.rend() &&
> > > -      (!CurrentRequest || CurrentRequest-
> > >UpdateType.hasValue())) {
> > > -    Lock.unlock();
> > > -    return Callback(getPossiblyStalePreamble());
> > > -  }
> > > -  assert(!RunSync && "Running synchronously, but queue is non-
> > > empty!");
> > > -  Requests.insert(LastUpdate.base(),
> > > -                  Request{[Callback = std::move(Callback),
> > this]()
> > > mutable {
> > > -                           
> > Callback(getPossiblyStalePreamble());
> > > -                          },
> > > -                          "GetPreamble", steady_clock::now(),
> > > -                          Context::current().clone(),
> > > -                          /*UpdateType=*/None,
> > > -                         
> > /*InvalidationPolicy=*/TUScheduler::NoInva
> > > lidation,
> > > -                          /*Invalidate=*/nullptr});
> > > -  Lock.unlock();
> > > -  RequestsCV.notify_all();
> > > -}
> > > -
> > >  void ASTWorker::waitForFirstPreamble() const {
> > > BuiltFirstPreamble.wait(); }
> > >  
> > >  tooling::CompileCommand ASTWorker::getCurrentCompileCommand()
> > const
> > > {
> > > @@ -1307,41 +1277,21 @@ void
> > > TUScheduler::runWithPreamble(llvm::StringRef Name, PathRef File,
> > >      return;
> > >    }
> > >  
> > > -  // Future is populated if the task needs a specific preamble.
> > > -  std::future<std::shared_ptr<const PreambleData>>
> > > ConsistentPreamble;
> > > -  // FIXME: Currently this only holds because ASTWorker blocks
> > after
> > > issuing a
> > > -  // preamble build. Get rid of consistent reads or make them
> > build
> > > on the
> > > -  // calling thread instead.
> > > -  if (Consistency == Consistent) {
> > > -    std::promise<std::shared_ptr<const PreambleData>> Promise;
> > > -    ConsistentPreamble = Promise.get_future();
> > > -    It->second->Worker->getCurrentPreamble(
> > > -        [Promise = std::move(Promise)](
> > > -            std::shared_ptr<const PreambleData> Preamble)
> > mutable {
> > > -          Promise.set_value(std::move(Preamble));
> > > -        });
> > > -  }
> > > -
> > >    std::shared_ptr<const ASTWorker> Worker = It->second-
> > > >Worker.lock();
> > >    auto Task =
> > >        [Worker, Consistency, Name = Name.str(), File =
> > File.str(),
> > >         Contents = It->second->Contents,
> > >         Command = Worker->getCurrentCompileCommand(),
> > >         Ctx = Context::current().derive(kFileBeingProcessed,
> > > std::string(File)),
> > > -       ConsistentPreamble = std::move(ConsistentPreamble),
> > >         Action = std::move(Action), this]() mutable {
> > >          std::shared_ptr<const PreambleData> Preamble;
> > > -        if (ConsistentPreamble.valid()) {
> > > -          Preamble = ConsistentPreamble.get();
> > > -        } else {
> > > -          if (Consistency != PreambleConsistency::StaleOrAbsent)
> > {
> > > -            // Wait until the preamble is built for the first
> > time,
> > > if preamble
> > > -            // is required. This avoids extra work of processing
> > the
> > > preamble
> > > -            // headers in parallel multiple times.
> > > -            Worker->waitForFirstPreamble();
> > > -          }
> > > -          Preamble = Worker->getPossiblyStalePreamble();
> > > +        if (Consistency == PreambleConsistency::Stale) {
> > > +          // Wait until the preamble is built for the first
> > time, if
> > > preamble
> > > +          // is required. This avoids extra work of processing
> > the
> > > preamble
> > > +          // headers in parallel multiple times.
> > > +          Worker->waitForFirstPreamble();
> > >          }
> > > +        Preamble = Worker->getPossiblyStalePreamble();
> > >  
> > >          std::lock_guard<Semaphore> BarrierLock(Barrier);
> > >          WithContext Guard(std::move(Ctx));
> > > 
> > > diff  --git a/clang-tools-extra/clangd/TUScheduler.h b/clang-
> > tools-
> > > extra/clangd/TUScheduler.h
> > > index a7f5d2f493fb..48ed2c76f546 100644
> > > --- a/clang-tools-extra/clangd/TUScheduler.h
> > > +++ b/clang-tools-extra/clangd/TUScheduler.h
> > > @@ -256,11 +256,6 @@ class TUScheduler {
> > >  
> > >    /// Controls whether preamble reads wait for the preamble to
> > be
> > > up-to-date.
> > >    enum PreambleConsistency {
> > > -    /// The preamble is generated from the current version of
> > the
> > > file.
> > > -    /// If the content was recently updated, we will wait until
> > we
> > > have a
> > > -    /// preamble that reflects that update.
> > > -    /// This is the slowest option, and may be delayed by other
> > > tasks.
> > > -    Consistent,
> > >      /// The preamble may be generated from an older version of
> > the
> > > file.
> > >      /// Reading from locations in the preamble may cause files
> > to be
> > > re-read.
> > >      /// This gives callers two options:
> > > 
> > > diff  --git a/clang-tools-extra/clangd/unittests/CMakeLists.txt
> > > b/clang-tools-extra/clangd/unittests/CMakeLists.txt
> > > index 541367a1d96a..4119445b85a0 100644
> > > --- a/clang-tools-extra/clangd/unittests/CMakeLists.txt
> > > +++ b/clang-tools-extra/clangd/unittests/CMakeLists.txt
> > > @@ -59,6 +59,7 @@ add_unittest(ClangdUnitTests ClangdTests
> > >    LSPClient.cpp
> > >    ParsedASTTests.cpp
> > >    PathMappingTests.cpp
> > > +  PreambleTests.cpp
> > >    PrintASTTests.cpp
> > >    QualityTests.cpp
> > >    RenameTests.cpp
> > > 
> > > diff  --git a/clang-tools-
> > > extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-
> > > extra/clangd/unittests/CodeCompleteTests.cpp
> > > index 794b21ee61a5..47637024ab91 100644
> > > --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
> > > +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
> > > @@ -1186,6 +1186,31 @@ TEST(SignatureHelpTest, OpeningParen) {
> > >    }
> > >  }
> > >  
> > > +TEST(SignatureHelpTest, StalePreamble) {
> > > +  TestTU TU;
> > > +  TU.Code = "";
> > > +  IgnoreDiagnostics Diags;
> > > +  auto Inputs = TU.inputs();
> > > +  auto CI = buildCompilerInvocation(Inputs, Diags);
> > > +  ASSERT_TRUE(CI);
> > > +  auto EmptyPreamble = buildPreamble(testPath(TU.Filename), *CI,
> > > Inputs,
> > > +                                     /*InMemory=*/true,
> > > /*Callback=*/nullptr);
> > > +  ASSERT_TRUE(EmptyPreamble);
> > > +
> > > +  TU.AdditionalFiles["a.h"] = "int foo(int x);";
> > > +  const Annotations Test(R"cpp(
> > > +    #include "a.h"
> > > +    void bar() { foo(^2); })cpp");
> > > +  TU.Code = Test.code().str();
> > > +  Inputs = TU.inputs();
> > > +  auto Results =
> > > +      signatureHelp(testPath(TU.Filename),
> > Inputs.CompileCommand,
> > > +                    *EmptyPreamble, TU.Code, Test.point(),
> > > Inputs.FS, nullptr);
> > > +  EXPECT_THAT(Results.signatures, ElementsAre(Sig("foo([[int
> > x]]) ->
> > > int")));
> > > +  EXPECT_EQ(0, Results.activeSignature);
> > > +  EXPECT_EQ(0, Results.activeParameter);
> > > +}
> > > +
> > >  class IndexRequestCollector : public SymbolIndex {
> > >  public:
> > >    bool
> > > 
> > > diff  --git a/clang-tools-
> > extra/clangd/unittests/PreambleTests.cpp
> > > b/clang-tools-extra/clangd/unittests/PreambleTests.cpp
> > > new file mode 100644
> > > index 000000000000..2815ca0a46f1
> > > --- /dev/null
> > > +++ b/clang-tools-extra/clangd/unittests/PreambleTests.cpp
> > > @@ -0,0 +1,123 @@
> > > +//===--- PreambleTests.cpp -----------------------------------
> > ---*-
> > > 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
> > > +//
> > > +//===-----------------------------------------------------------
> > ----
> > > -------===//
> > > +
> > > +#include "Annotations.h"
> > > +#include "Compiler.h"
> > > +#include "Preamble.h"
> > > +#include "TestFS.h"
> > > +#include "clang/Lex/PreprocessorOptions.h"
> > > +#include "llvm/ADT/STLExtras.h"
> > > +#include "llvm/ADT/StringRef.h"
> > > +#include "gmock/gmock.h"
> > > +#include "gtest/gtest.h"
> > > +#include <string>
> > > +#include <vector>
> > > +
> > > +namespace clang {
> > > +namespace clangd {
> > > +namespace {
> > > +
> > > +using testing::_;
> > > +using testing::Contains;
> > > +using testing::Pair;
> > > +
> > > +MATCHER_P(HasContents, Contents, "") { return arg->getBuffer()
> > ==
> > > Contents; }
> > > +
> > > +TEST(PreamblePatchTest, IncludeParsing) {
> > > +  MockFSProvider FS;
> > > +  MockCompilationDatabase CDB;
> > > +  IgnoreDiagnostics Diags;
> > > +  ParseInputs PI;
> > > +  PI.FS = FS.getFileSystem();
> > > +
> > > +  // We expect any line with a point to show up in the patch.
> > > +  llvm::StringRef Cases[] = {
> > > +      // Only preamble
> > > +      R"cpp(^#include "a.h")cpp",
> > > +      // Both preamble and mainfile
> > > +      R"cpp(
> > > +        ^#include "a.h"
> > > +        garbage, finishes preamble
> > > +        #include "a.h")cpp",
> > > +      // Mixed directives
> > > +      R"cpp(
> > > +        ^#include "a.h"
> > > +        #pragma directive
> > > +        // some comments
> > > +        ^#include_next <a.h>
> > > +        #ifdef skipped
> > > +        ^#import "a.h"
> > > +        #endif)cpp",
> > > +      // Broken directives
> > > +      R"cpp(
> > > +        #include "a
> > > +        ^#include "a.h"
> > > +        #include <b
> > > +        ^#include <b.h>)cpp",
> > > +  };
> > > +
> > > +  const auto FileName = testPath("foo.cc");
> > > +  for (const auto Case : Cases) {
> > > +    Annotations Test(Case);
> > > +    const auto Code = Test.code();
> > > +    PI.CompileCommand = *CDB.getCompileCommand(FileName);
> > > +
> > > +    SCOPED_TRACE(Code);
> > > +    // Check preamble lexing logic by building an empty preamble
> > and
> > > patching it
> > > +    // with all the contents.
> > > +    PI.Contents = "";
> > > +    const auto CI = buildCompilerInvocation(PI, Diags);
> > > +    const auto EmptyPreamble = buildPreamble(FileName, *CI, PI,
> > > true, nullptr);
> > > +    PI.Contents = Code.str();
> > > +
> > > +    std::string ExpectedBuffer;
> > > +    const auto Points = Test.points();
> > > +    for (const auto &P : Points) {
> > > +      // Copy the whole line.
> > > +      auto StartOffset = llvm::cantFail(positionToOffset(Code,
> > P));
> > > +      ExpectedBuffer.append(Code.substr(StartOffset)
> > > +                                .take_until([](char C) { return
> > C ==
> > > '\n'; })
> > > +                                .str());
> > > +      ExpectedBuffer += '\n';
> > > +    }
> > > +
> > > +    PreamblePatch::create(FileName, PI,
> > *EmptyPreamble).apply(*CI);
> > > +    EXPECT_THAT(CI->getPreprocessorOpts().RemappedFileBuffers,
> > > +                Contains(Pair(_, HasContents(ExpectedBuffer))));
> > > +  }
> > > +}
> > > +
> > > +TEST(PreamblePatchTest, ContainsNewIncludes) {
> > > +  MockFSProvider FS;
> > > +  MockCompilationDatabase CDB;
> > > +  IgnoreDiagnostics Diags;
> > > +
> > > +  const auto FileName = testPath("foo.cc");
> > > +  ParseInputs PI;
> > > +  PI.FS = FS.getFileSystem();
> > > +  PI.CompileCommand = *CDB.getCompileCommand(FileName);
> > > +  PI.Contents = "#include <existing.h>\n";
> > > +
> > > +  // Check 
> > > diff ing logic by adding a new header to the preamble and
> > ensuring
> > > +  // only it is patched.
> > > +  const auto CI = buildCompilerInvocation(PI, Diags);
> > > +  const auto FullPreamble = buildPreamble(FileName, *CI, PI,
> > true,
> > > nullptr);
> > > +
> > > +  constexpr llvm::StringLiteral Patch =
> > > +      "#include <test>\n#import <existing.h>\n";
> > > +  // We provide the same includes twice, they shouldn't be
> > included
> > > in the
> > > +  // patch.
> > > +  PI.Contents = (Patch + PI.Contents + PI.Contents).str();
> > > +  PreamblePatch::create(FileName, PI, *FullPreamble).apply(*CI);
> > > +  EXPECT_THAT(CI->getPreprocessorOpts().RemappedFileBuffers,
> > > +              Contains(Pair(_, HasContents(Patch))));
> > > +}
> > > +
> > > +} // namespace
> > > +} // namespace clangd
> > > +} // namespace clang
> > > 
> > > diff  --git a/clang-tools-
> > extra/clangd/unittests/TUSchedulerTests.cpp 
> > > b/clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp
> > > index 7106e01a10e4..6f50a5acd4e3 100644
> > > --- a/clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp
> > > +++ b/clang-tools-extra/clangd/unittests/TUSchedulerTests.cpp
> > > @@ -246,66 +246,6 @@ TEST_F(TUSchedulerTests, Debounce) {
> > >    EXPECT_EQ(2, CallbackCount);
> > >  }
> > >  
> > > -static std::vector<std::string> includes(const PreambleData
> > > *Preamble) {
> > > -  std::vector<std::string> Result;
> > > -  if (Preamble)
> > > -    for (const auto &Inclusion : Preamble-
> > > >Includes.MainFileIncludes)
> > > -      Result.push_back(Inclusion.Written);
> > > -  return Result;
> > > -}
> > > -
> > > -TEST_F(TUSchedulerTests, PreambleConsistency) {
> > > -  std::atomic<int> CallbackCount(0);
> > > -  {
> > > -    Notification InconsistentReadDone; // Must live longest.
> > > -    TUScheduler S(CDB, optsForTest());
> > > -    auto Path = testPath("foo.cpp");
> > > -    // Schedule two updates (A, B) and two preamble reads
> > (stale,
> > > consistent).
> > > -    // The stale read should see A, and the consistent read
> > should
> > > see B.
> > > -    // (We recognize the preambles by their included files).
> > > -    auto Inputs = getInputs(Path, "#include <A>");
> > > -    Inputs.Version = "A";
> > > -    updateWithCallback(S, Path, Inputs, WantDiagnostics::Yes,
> > [&]()
> > > {
> > > -      // This callback runs in between the two preamble updates.
> > > -
> > > -      // This blocks update B, preventing it from winning the
> > race
> > > -      // against the stale read.
> > > -      // If the first read was instead consistent, this would
> > > deadlock.
> > > -      InconsistentReadDone.wait();
> > > -      // This delays update B, preventing it from winning a race
> > > -      // against the consistent read. The consistent read sees B
> > > -      // only because it waits for it.
> > > -      // If the second read was stale, it would usually see A.
> > > -     
> > std::this_thread::sleep_for(std::chrono::milliseconds(100));
> > > -    });
> > > -    Inputs.Contents = "#include <B>";
> > > -    Inputs.Version = "B";
> > > -    S.update(Path, Inputs, WantDiagnostics::Yes);
> > > -
> > > -    S.runWithPreamble("StaleRead", Path, TUScheduler::Stale,
> > > -                      [&](Expected<InputsAndPreamble> Pre) {
> > > -                        ASSERT_TRUE(bool(Pre));
> > > -                        ASSERT_TRUE(Pre->Preamble);
> > > -                        EXPECT_EQ(Pre->Preamble->Version, "A");
> > > -                        EXPECT_THAT(includes(Pre->Preamble),
> > > -                                    ElementsAre("<A>"));
> > > -                        InconsistentReadDone.notify();
> > > -                        ++CallbackCount;
> > > -                      });
> > > -    S.runWithPreamble("ConsistentRead", Path,
> > > TUScheduler::Consistent,
> > > -                      [&](Expected<InputsAndPreamble> Pre) {
> > > -                        ASSERT_TRUE(bool(Pre));
> > > -                        ASSERT_TRUE(Pre->Preamble);
> > > -                        EXPECT_EQ(Pre->Preamble->Version, "B");
> > > -                        EXPECT_THAT(includes(Pre->Preamble),
> > > -                                    ElementsAre("<B>"));
> > > -                        ++CallbackCount;
> > > -                      });
> > > -    S.blockUntilIdle(timeoutSeconds(10));
> > > -  }
> > > -  EXPECT_EQ(2, CallbackCount);
> > > -}
> > > -
> > >  TEST_F(TUSchedulerTests, Cancellation) {
> > >    // We have the following update/read sequence
> > >    //   U0
> > > 
> > > diff  --git a/clang/include/clang/Basic/TokenKinds.h
> > > b/clang/include/clang/Basic/TokenKinds.h
> > > index c25181e6827c..4e66aa1c8c2d 100644
> > > --- a/clang/include/clang/Basic/TokenKinds.h
> > > +++ b/clang/include/clang/Basic/TokenKinds.h
> > > @@ -14,6 +14,7 @@
> > >  #ifndef LLVM_CLANG_BASIC_TOKENKINDS_H
> > >  #define LLVM_CLANG_BASIC_TOKENKINDS_H
> > >  
> > > +#include "llvm/ADT/DenseMapInfo.h"
> > >  #include "llvm/Support/Compiler.h"
> > >  
> > >  namespace clang {
> > > @@ -95,7 +96,25 @@ bool isAnnotation(TokenKind K);
> > >  /// Return true if this is an annotation token representing a
> > > pragma.
> > >  bool isPragmaAnnotation(TokenKind K);
> > >  
> > > -}  // end namespace tok
> > > -}  // end namespace clang
> > > +} // end namespace tok
> > > +} // end namespace clang
> > > +
> > > +namespace llvm {
> > > +template <> struct DenseMapInfo<clang::tok::PPKeywordKind> {
> > > +  static inline clang::tok::PPKeywordKind getEmptyKey() {
> > > +    return clang::tok::PPKeywordKind::pp_not_keyword;
> > > +  }
> > > +  static inline clang::tok::PPKeywordKind getTombstoneKey() {
> > > +    return clang::tok::PPKeywordKind::NUM_PP_KEYWORDS;
> > > +  }
> > > +  static unsigned getHashValue(const clang::tok::PPKeywordKind
> > &Val)
> > > {
> > > +    return static_cast<unsigned>(Val);
> > > +  }
> > > +  static bool isEqual(const clang::tok::PPKeywordKind &LHS,
> > > +                      const clang::tok::PPKeywordKind &RHS) {
> > > +    return LHS == RHS;
> > > +  }
> > > +};
> > > +} // namespace llvm
> > >  
> > >  #endif
> > > 
> > > diff  --git a/clang/include/clang/Frontend/PrecompiledPreamble.h
> > > b/clang/include/clang/Frontend/PrecompiledPreamble.h
> > > index 0d95ee683eee..538f6c92ad55 100644
> > > --- a/clang/include/clang/Frontend/PrecompiledPreamble.h
> > > +++ b/clang/include/clang/Frontend/PrecompiledPreamble.h
> > > @@ -16,6 +16,7 @@
> > >  #include "clang/Lex/Lexer.h"
> > >  #include "clang/Lex/Preprocessor.h"
> > >  #include "llvm/ADT/IntrusiveRefCntPtr.h"
> > > +#include "llvm/ADT/StringRef.h"
> > >  #include "llvm/Support/AlignOf.h"
> > >  #include "llvm/Support/MD5.h"
> > >  #include <cstddef>
> > > @@ -94,6 +95,11 @@ class PrecompiledPreamble {
> > >    /// be used for logging and debugging purposes only.
> > >    std::size_t getSize() const;
> > >  
> > > +  /// Returned string is not null-terminated.
> > > +  llvm::StringRef getContents() const {
> > > +    return {PreambleBytes.data(), PreambleBytes.size()};
> > > +  }
> > > +
> > >    /// Check whether PrecompiledPreamble can be reused for the
> > new
> > > contents(\p
> > >    /// MainFileBuffer) of the main file.
> > >    bool CanReuse(const CompilerInvocation &Invocation,
> > > 
> > > 
> > >         
> > > _______________________________________________
> > > cfe-commits mailing list
> > > cfe-commits at lists.llvm.org
> > > https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


More information about the cfe-commits mailing list