https://github.com/ArcsinX created 
https://github.com/llvm/llvm-project/pull/189284

This option allows to disable preamble optimization in clangd. By default it's 
false, but became true if experimental modules support is enabled.

This PR is a try to address C++20 modules problems described here 
https://github.com/llvm/llvm-project/pull/187432

Set to WIP because no tests were added yet.
@ChuanqiXu9 can you please take a look, is this solutions is acceptable to you? 
For me this fixes all crashes related to including headers with import inside

>From 7c0a8cbff6e0fb405ca75f1688a8dab92325f63f Mon Sep 17 00:00:00 2001
From: Aleksandr Platonov <[email protected]>
Date: Sun, 29 Mar 2026 23:07:37 +0300
Subject: [PATCH] [clangd] Introduce --skip-preamble-build command line options

This option allows to disable preamble optimization in clangd.
By default it's false, but became true if experimental modules support is 
enabled.
---
 clang-tools-extra/clangd/ClangdServer.cpp    |  3 +++
 clang-tools-extra/clangd/ClangdServer.h      |  5 ++++
 clang-tools-extra/clangd/CodeComplete.cpp    |  8 ++++--
 clang-tools-extra/clangd/Compiler.h          |  2 ++
 clang-tools-extra/clangd/ParsedAST.cpp       |  2 +-
 clang-tools-extra/clangd/Preamble.cpp        | 27 +++++++++++++++-----
 clang-tools-extra/clangd/Preamble.h          |  4 +++
 clang-tools-extra/clangd/tool/ClangdMain.cpp | 18 +++++++++++++
 8 files changed, 59 insertions(+), 10 deletions(-)

diff --git a/clang-tools-extra/clangd/ClangdServer.cpp 
b/clang-tools-extra/clangd/ClangdServer.cpp
index f1a87dd12d905..af80cc9c3e453 100644
--- a/clang-tools-extra/clangd/ClangdServer.cpp
+++ b/clang-tools-extra/clangd/ClangdServer.cpp
@@ -223,6 +223,7 @@ ClangdServer::ClangdServer(const GlobalCompilationDatabase 
&CDB,
       UseDirtyHeaders(Opts.UseDirtyHeaders),
       LineFoldingOnly(Opts.LineFoldingOnly),
       PreambleParseForwardingFunctions(Opts.PreambleParseForwardingFunctions),
+      SkipPreambleBuild(Opts.SkipPreambleBuild),
       ImportInsertions(Opts.ImportInsertions),
       PublishInactiveRegions(Opts.PublishInactiveRegions),
       WorkspaceRoot(Opts.WorkspaceRoot),
@@ -300,6 +301,7 @@ void ClangdServer::addDocument(PathRef File, 
llvm::StringRef Contents,
   std::string ActualVersion = DraftMgr.addDraft(File, Version, Contents);
   ParseOptions Opts;
   Opts.PreambleParseForwardingFunctions = PreambleParseForwardingFunctions;
+  Opts.SkipPreambleBuild = SkipPreambleBuild;
   Opts.ImportInsertions = ImportInsertions;
 
   // Compile command is set asynchronously during update, as it can be slow.
@@ -459,6 +461,7 @@ void ClangdServer::codeComplete(PathRef File, Position Pos,
         Config::current().Completion.HeaderInsertion;
     CodeCompleteOpts.CodePatterns = Config::current().Completion.CodePatterns;
     CodeCompleteOpts.MacroFilter = Config::current().Completion.MacroFilter;
+    ParseInput.Opts.SkipPreambleBuild = SkipPreambleBuild;
     // FIXME(ibiryukov): even if Preamble is non-null, we may want to check
     // both the old and the new version in case only one of them matches.
     CodeCompleteResult Result = clangd::codeComplete(
diff --git a/clang-tools-extra/clangd/ClangdServer.h 
b/clang-tools-extra/clangd/ClangdServer.h
index 3ffaf67553dce..6ee1dfd863e3c 100644
--- a/clang-tools-extra/clangd/ClangdServer.h
+++ b/clang-tools-extra/clangd/ClangdServer.h
@@ -191,6 +191,9 @@ class ClangdServer {
     // If true, parse emplace-like functions in the preamble.
     bool PreambleParseForwardingFunctions = true;
 
+    // If true, skip preamble build.
+    bool SkipPreambleBuild = false;
+
     /// Whether include fixer insertions for Objective-C code should use 
#import
     /// instead of #include.
     bool ImportInsertions = false;
@@ -508,6 +511,8 @@ class ClangdServer {
 
   bool PreambleParseForwardingFunctions = true;
 
+  bool SkipPreambleBuild = false;
+
   bool ImportInsertions = false;
 
   bool PublishInactiveRegions = false;
diff --git a/clang-tools-extra/clangd/CodeComplete.cpp 
b/clang-tools-extra/clangd/CodeComplete.cpp
index 28f81cd5267d5..8414ffe20526a 100644
--- a/clang-tools-extra/clangd/CodeComplete.cpp
+++ b/clang-tools-extra/clangd/CodeComplete.cpp
@@ -1420,7 +1420,8 @@ bool 
semaCodeComplete(std::unique_ptr<CodeCompleteConsumer> Consumer,
   // overriding the preamble will break sema completion. Fortunately we can 
just
   // skip all includes in this case; these completions are really simple.
   PreambleBounds PreambleRegion =
-      ComputePreambleBounds(CI->getLangOpts(), *ContentsBuffer, 0);
+      computePreambleBounds(CI->getLangOpts(), *ContentsBuffer,
+                            Input.ParseInput.Opts.SkipPreambleBuild);
   bool CompletingInPreamble = Input.Offset < PreambleRegion.Size ||
                               (!PreambleRegion.PreambleEndsAtStartOfLine &&
                                Input.Offset == PreambleRegion.Size);
@@ -1433,7 +1434,10 @@ bool 
semaCodeComplete(std::unique_ptr<CodeCompleteConsumer> Consumer,
   if (Input.Preamble.StatCache)
     VFS = Input.Preamble.StatCache->getConsumingFS(std::move(VFS));
   auto Clang = prepareCompilerInstance(
-      std::move(CI), !CompletingInPreamble ? &Input.Preamble.Preamble : 
nullptr,
+      std::move(CI),
+      (!CompletingInPreamble && !Input.ParseInput.Opts.SkipPreambleBuild)
+          ? &Input.Preamble.Preamble
+          : nullptr,
       std::move(ContentsBuffer), std::move(VFS), IgnoreDiags);
   Clang->getPreprocessorOpts().SingleFileParseMode = CompletingInPreamble;
   Clang->setCodeCompletionConsumer(Consumer.release());
diff --git a/clang-tools-extra/clangd/Compiler.h 
b/clang-tools-extra/clangd/Compiler.h
index e513e4c40794a..5e5e23d5b9682 100644
--- a/clang-tools-extra/clangd/Compiler.h
+++ b/clang-tools-extra/clangd/Compiler.h
@@ -43,6 +43,8 @@ struct ParseOptions {
   bool PreambleParseForwardingFunctions = true;
 
   bool ImportInsertions = false;
+
+  bool SkipPreambleBuild = false;
 };
 
 /// Information required to run clang, e.g. to parse AST or do code completion.
diff --git a/clang-tools-extra/clangd/ParsedAST.cpp 
b/clang-tools-extra/clangd/ParsedAST.cpp
index 4e873f1257a17..e2a49f384a3e9 100644
--- a/clang-tools-extra/clangd/ParsedAST.cpp
+++ b/clang-tools-extra/clangd/ParsedAST.cpp
@@ -464,7 +464,7 @@ ParsedAST::build(llvm::StringRef Filename, const 
ParseInputs &Inputs,
     Patch->apply(*CI);
   }
   auto Clang = prepareCompilerInstance(
-      std::move(CI), PreamblePCH,
+      std::move(CI), Inputs.Opts.SkipPreambleBuild ? nullptr : PreamblePCH,
       llvm::MemoryBuffer::getMemBufferCopy(Inputs.Contents, Filename), VFS,
       *DiagConsumer);
 
diff --git a/clang-tools-extra/clangd/Preamble.cpp 
b/clang-tools-extra/clangd/Preamble.cpp
index f5e512793e98e..58da7edcf3b93 100644
--- a/clang-tools-extra/clangd/Preamble.cpp
+++ b/clang-tools-extra/clangd/Preamble.cpp
@@ -320,8 +320,9 @@ struct ScannedPreamble {
 /// running preprocessor over \p Contents. Returned includes do not contain
 /// resolved paths. \p Cmd is used to build the compiler invocation, which 
might
 /// stat/read files.
-llvm::Expected<ScannedPreamble>
-scanPreamble(llvm::StringRef Contents, const tooling::CompileCommand &Cmd) {
+llvm::Expected<ScannedPreamble> scanPreamble(llvm::StringRef Contents,
+                                             const tooling::CompileCommand 
&Cmd,
+                                             bool SkipPreambleBuild) {
   class EmptyFS : public ThreadsafeFS {
   private:
     llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> viewImpl() const override {
@@ -345,7 +346,8 @@ scanPreamble(llvm::StringRef Contents, const 
tooling::CompileCommand &Cmd) {
   // This means we're scanning (though not preprocessing) the preamble section
   // twice. However, it's important to precisely follow the preamble bounds 
used
   // elsewhere.
-  auto Bounds = ComputePreambleBounds(CI->getLangOpts(), *ContentsBuffer, 0);
+  auto Bounds = computePreambleBounds(CI->getLangOpts(), *ContentsBuffer,
+                                      SkipPreambleBuild);
   auto PreambleContents = llvm::MemoryBuffer::getMemBufferCopy(
       llvm::StringRef(PI.Contents).take_front(Bounds.Size));
   auto Clang = prepareCompilerInstance(
@@ -576,7 +578,8 @@ buildPreamble(PathRef FileName, CompilerInvocation CI,
   // without those.
   auto ContentsBuffer =
       llvm::MemoryBuffer::getMemBuffer(Inputs.Contents, FileName);
-  auto Bounds = ComputePreambleBounds(CI.getLangOpts(), *ContentsBuffer, 0);
+  auto Bounds = computePreambleBounds(CI.getLangOpts(), *ContentsBuffer,
+                                      Inputs.Opts.SkipPreambleBuild);
 
   trace::Span Tracer("BuildPreamble");
   SPAN_ATTACH(Tracer, "File", FileName);
@@ -722,7 +725,8 @@ bool isPreambleCompatible(const PreambleData &Preamble,
                           const CompilerInvocation &CI) {
   auto ContentsBuffer =
       llvm::MemoryBuffer::getMemBuffer(Inputs.Contents, FileName);
-  auto Bounds = ComputePreambleBounds(CI.getLangOpts(), *ContentsBuffer, 0);
+  auto Bounds = computePreambleBounds(CI.getLangOpts(), *ContentsBuffer,
+                                      Inputs.Opts.SkipPreambleBuild);
   auto VFS = Inputs.TFS->view(Inputs.CompileCommand.Directory);
   return compileCommandsAreEqual(Inputs.CompileCommand,
                                  Preamble.CompileCommand) &&
@@ -785,13 +789,15 @@ PreamblePatch PreamblePatch::create(llvm::StringRef 
FileName,
   // - If scanning for Modified fails, cannot figure out newly added ones so
   //   there's nothing to do but generate an empty patch.
   auto BaselineScan =
-      scanPreamble(Baseline.Preamble.getContents(), Modified.CompileCommand);
+      scanPreamble(Baseline.Preamble.getContents(), Modified.CompileCommand,
+                   Modified.Opts.SkipPreambleBuild);
   if (!BaselineScan) {
     elog("Failed to scan baseline of {0}: {1}", FileName,
          BaselineScan.takeError());
     return PreamblePatch::unmodified(Baseline);
   }
-  auto ModifiedScan = scanPreamble(Modified.Contents, Modified.CompileCommand);
+  auto ModifiedScan = scanPreamble(Modified.Contents, Modified.CompileCommand,
+                                   Modified.Opts.SkipPreambleBuild);
   if (!ModifiedScan) {
     elog("Failed to scan modified contents of {0}: {1}", FileName,
          ModifiedScan.takeError());
@@ -957,5 +963,12 @@ OptionalFileEntryRef 
PreamblePatch::getPatchEntry(llvm::StringRef MainFilePath,
   auto PatchFilePath = getPatchName(MainFilePath);
   return SM.getFileManager().getOptionalFileRef(PatchFilePath);
 }
+
+PreambleBounds computePreambleBounds(const LangOptions &LangOpts,
+                                     const llvm::MemoryBufferRef &Buffer,
+                                     bool SkipPreambleBuild) {
+  return SkipPreambleBuild ? PreambleBounds(0, false)
+                           : ComputePreambleBounds(LangOpts, Buffer, 0);
+}
 } // namespace clangd
 } // namespace clang
diff --git a/clang-tools-extra/clangd/Preamble.h 
b/clang-tools-extra/clangd/Preamble.h
index 7f2eb233ee1aa..2051930dae05d 100644
--- a/clang-tools-extra/clangd/Preamble.h
+++ b/clang-tools-extra/clangd/Preamble.h
@@ -239,6 +239,10 @@ class PreamblePatch {
   MainFileMacros PatchedMacros;
 };
 
+PreambleBounds computePreambleBounds(const LangOptions &LangOpts,
+                                     const llvm::MemoryBufferRef &Buffer,
+                                     bool SkipPreambleBuild);
+
 } // namespace clangd
 } // namespace clang
 
diff --git a/clang-tools-extra/clangd/tool/ClangdMain.cpp 
b/clang-tools-extra/clangd/tool/ClangdMain.cpp
index 54af3662470db..7bedf83be14ac 100644
--- a/clang-tools-extra/clangd/tool/ClangdMain.cpp
+++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp
@@ -525,6 +525,14 @@ opt<bool> PreambleParseForwardingFunctions{
     init(ParseOptions().PreambleParseForwardingFunctions),
 };
 
+opt<bool> SkipPreambleBuild{
+    "skip-preamble-build",
+    cat(Misc),
+    desc("If ture, skip preamble build"),
+    Hidden,
+    init(ParseOptions().SkipPreambleBuild),
+};
+
 #if defined(__GLIBC__) && CLANGD_MALLOC_TRIM
 opt<bool> EnableMallocTrim{
     "malloc-trim",
@@ -1005,6 +1013,16 @@ clangd accepts flags on the commandline, and in the 
CLANGD_FLAGS environment var
   }
   Opts.UseDirtyHeaders = UseDirtyHeaders;
   Opts.PreambleParseForwardingFunctions = PreambleParseForwardingFunctions;
+  if (ExperimentalModulesSupport &&
+      SkipPreambleBuild.getNumOccurrences() == 0) {
+    Opts.SkipPreambleBuild = true;
+    log("Experimental C++20 modules support is enabled. This leads to "
+        "disabling preamble build due to instability of mixed precompiled "
+        "headers and C++20 modules. To enable C++20 modules together with "
+        "preamble builds, pass --skip-preamble-build=false command line "
+        "option");
+  } else
+    Opts.SkipPreambleBuild = SkipPreambleBuild;
   Opts.ImportInsertions = ImportInsertions;
   Opts.QueryDriverGlobs = std::move(QueryDriverGlobs);
   Opts.TweakFilter = [&](const Tweak &T) {

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to