Skip to content

Conversation

@aengelke
Copy link
Contributor

@aengelke aengelke commented Jan 16, 2026

This patch implements PCH support. PCH is enabled by default, unless noted below, and can be disabled with -DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON.

  • Libraries can defined precompiled headers using a newly added PRECOMPILE_HEADERS keyword. If specified, the listed headers will be compiled into a pre-compiled header using standard CMake mechanisms.

  • Libraries that don't define their own PRECOMPILE_HEADERS but directly depend on a library or component that defines its own PCH will reuse that PCH. This reuse is not transitive to prevent excessive use of unrelated headers. If multiple dependencies provide a reusable PCH, the first one with the longest dependency chain (stored in the CMake target property LLVM_PCH_PRIORITY) is used. However, due to CMake limitations, only PCH from targets that are already defined can be reused; therefore libraries that should reuse a PCH must be defined later in the CMake file (=> add_subdirectory order matters).

  • Libraries and executables can prevent PCH reuse with the keyword DISABLE_PCH_REUSE. This both prevents reuse from dependencies and reuse by other dependants. This is useful when, e.g., internal headers are used in the PCH or the used headers are unlikely to provide benefits for dependants.

  • Precompiled headers are only used for C++ targets. Due to CMake-weirdness, C-only targets must explicitly disable PCH reuse, otherwise configuration fails.

  • With GCC, PCH provide very little benefits (tested with GCC 14 and 15) due to increased template instatiation costs, but substantially increase max-rss and build directory size. Therefore, disable PCH with GCC by default; this can be explicitly overridden on the command line with -DCMAKE_DISABLE_PRECOMPILE_HEADERS=OFF.

  • With ccache and non-Clang compilers, changes in macro definitions are not always accurately forwarded with ccache's preprocessed mode. To be on the safe side, when ccache is enabled, disable PCH with all non-Clang compilers; this can be explicitly overridden.

  • Add a base PCH to LLVMSupport, which includes widely used standard library and Support+ADT headers. The pch.h is placed in include so that later PCH headers can extend that list of headers.

  • Flang PCH use is ported to the general mechanism.

Addition of PCH headers for other components (e.g., IR, CodeGen) will be posted as separate PRs.

RFC: https://discourse.llvm.org/t/rfc-use-pre-compiled-headers-to-speed-up-llvm-build-by-1-5-2x/89345

This patch implements PCH support.

* Libraries can defined precompiled headers using a newly added
  PRECOMPILE_HEADERS keyword. If specified, the listed headers will be
  compiled into a pre-compiled header using standard CMake mechanisms.

* Libraries that don't define their own PRECOMPILE_HEADERS but directly
  depend on a library or component that defines its own PCH will reuse
  that PCH. This reuse is not transitive to prevent excessive use of
  unrelated headers. If multiple dependencies provide a reusable PCH,
  the first one with the longest dependency chain (stored in the CMake
  target property LLVM_PCH_PRIORITY) is used. However, due to CMake
  limitations, only PCH from targets that are already defined can be
  reused; therefore libraries that should reuse a PCH must be defined
  later in the CMake file (=> add_subdirectory order matters).

* Libraries and executables can prevent PCH reuse with the keyword
  DISABLE_PCH_REUSE. This both prevents reuse from dependencies and
  reuse by other dependants. This is useful when, e.g., internal headers
  are used in the PCH or the used headers are unlikely to provide
  benefits for dependants.

* Precompiled headers are only used for C++ targets. Due to
  CMake-weirdness, C-only targets must explicitly disable PCH reuse,
  otherwise configuration fails.

* With GCC, PCH provide very little benefits (tested with GCC 14 and
  15) due to increased template instatiation costs, but substantially
  increase max-rss and build directory size. Therefore, disable PCH with
  GCC by default; this can be explicitly overridden on the command line
  with -DCMAKE_DISABLE_PRECOMPILE_HEADERS=OFF.

* With ccache and non-Clang compilers, changes in macro definitions are
  not always accurately forwarded with ccache's preprocessed mode. To be
  on the safe side, when ccache is enabled, disable PCH with all
  non-Clang compilers; this can be explicitly overridden.

* Add a base PCH to LLVMSupport, which includes widely used standard
  library and Support+ADT headers. The pch.h is placed in include so
  that later PCH headers can extend that list of headers.

* Flang PCH use is ported to the general mechanism.

Addition of PCH headers for other components (e.g., IR, CodeGen) will be
posted afterwards as separate PRs.

RFC: https://discourse.llvm.org/t/rfc-use-pre-compiled-headers-to-speed-up-llvm-build-by-1-5-2x/89345
@llvmbot llvmbot added cmake Build system in general and CMake in particular flang:driver mlir flang Flang issues not falling into any other category llvm:support flang:fir-hlfir testing-tools flang:semantics flang:parser labels Jan 16, 2026
@llvmbot
Copy link
Member

llvmbot commented Jan 16, 2026

@llvm/pr-subscribers-infrastructure
@llvm/pr-subscribers-flang-parser
@llvm/pr-subscribers-mlir

@llvm/pr-subscribers-flang-driver

Author: Alexis Engelke (aengelke)

Changes

This patch implements PCH support.

  • Libraries can defined precompiled headers using a newly added PRECOMPILE_HEADERS keyword. If specified, the listed headers will be compiled into a pre-compiled header using standard CMake mechanisms.

  • Libraries that don't define their own PRECOMPILE_HEADERS but directly depend on a library or component that defines its own PCH will reuse that PCH. This reuse is not transitive to prevent excessive use of unrelated headers. If multiple dependencies provide a reusable PCH, the first one with the longest dependency chain (stored in the CMake target property LLVM_PCH_PRIORITY) is used. However, due to CMake limitations, only PCH from targets that are already defined can be reused; therefore libraries that should reuse a PCH must be defined later in the CMake file (=> add_subdirectory order matters).

  • Libraries and executables can prevent PCH reuse with the keyword DISABLE_PCH_REUSE. This both prevents reuse from dependencies and reuse by other dependants. This is useful when, e.g., internal headers are used in the PCH or the used headers are unlikely to provide benefits for dependants.

  • Precompiled headers are only used for C++ targets. Due to CMake-weirdness, C-only targets must explicitly disable PCH reuse, otherwise configuration fails.

  • With GCC, PCH provide very little benefits (tested with GCC 14 and 15) due to increased template instatiation costs, but substantially increase max-rss and build directory size. Therefore, disable PCH with GCC by default; this can be explicitly overridden on the command line with -DCMAKE_DISABLE_PRECOMPILE_HEADERS=OFF.

  • With ccache and non-Clang compilers, changes in macro definitions are not always accurately forwarded with ccache's preprocessed mode. To be on the safe side, when ccache is enabled, disable PCH with all non-Clang compilers; this can be explicitly overridden.

  • Add a base PCH to LLVMSupport, which includes widely used standard library and Support+ADT headers. The pch.h is placed in include so that later PCH headers can extend that list of headers.

  • Flang PCH use is ported to the general mechanism.

Addition of PCH headers for other components (e.g., IR, CodeGen) will be posted as separate PRs.

RFC: https://discourse.llvm.org/t/rfc-use-pre-compiled-headers-to-speed-up-llvm-build-by-1-5-2x/89345


Patch is 25.98 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/176420.diff

25 Files Affected:

  • (modified) clang/tools/c-index-test/CMakeLists.txt (+2)
  • (modified) clang/tools/clang-fuzzer/dictionary/CMakeLists.txt (+2)
  • (modified) flang/CMakeLists.txt (-10)
  • (modified) flang/lib/Evaluate/CMakeLists.txt (+9-9)
  • (modified) flang/lib/Frontend/CMakeLists.txt (+8-8)
  • (modified) flang/lib/Lower/CMakeLists.txt (+11-11)
  • (modified) flang/lib/Parser/CMakeLists.txt (+8-8)
  • (modified) flang/lib/Semantics/CMakeLists.txt (+9-9)
  • (modified) llvm/CMakeLists.txt (+16)
  • (modified) llvm/cmake/modules/AddLLVM.cmake (+69-8)
  • (modified) llvm/cmake/modules/HandleLLVMOptions.cmake (+21)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsAddObjectFile/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsBasicUsage/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsDumpObjects/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsIRTransforms/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsLazy/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsMCJITLikeMemoryManager/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsRemovableCode/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsVeryLazy/CMakeLists.txt (+2)
  • (added) llvm/include/llvm/Support/pch.h (+75)
  • (modified) llvm/lib/CMakeLists.txt (+1-1)
  • (modified) llvm/lib/Support/CMakeLists.txt (+6-1)
  • (modified) llvm/tools/llvm-c-test/CMakeLists.txt (+2)
  • (modified) llvm/utils/count/CMakeLists.txt (+2)
  • (modified) mlir/test/CAPI/CMakeLists.txt (+1)
diff --git a/clang/tools/c-index-test/CMakeLists.txt b/clang/tools/c-index-test/CMakeLists.txt
index 41e80e66ffa7a..2bbd387d6e812 100644
--- a/clang/tools/c-index-test/CMakeLists.txt
+++ b/clang/tools/c-index-test/CMakeLists.txt
@@ -5,6 +5,8 @@ set(LLVM_LINK_COMPONENTS
 add_clang_executable(c-index-test
   c-index-test.c
   core_main.cpp
+
+  DISABLE_PCH_REUSE # Prevent CMake error with C source files.
   )
 
 if(NOT MSVC)
diff --git a/clang/tools/clang-fuzzer/dictionary/CMakeLists.txt b/clang/tools/clang-fuzzer/dictionary/CMakeLists.txt
index 6b72b98f5e1c4..a7f18965b4e29 100644
--- a/clang/tools/clang-fuzzer/dictionary/CMakeLists.txt
+++ b/clang/tools/clang-fuzzer/dictionary/CMakeLists.txt
@@ -1,5 +1,7 @@
 set(CMAKE_CXX_FLAGS ${CXX_FLAGS_NOFUZZ})
 add_clang_executable(clang-fuzzer-dictionary
   dictionary.c
+
+  DISABLE_PCH_REUSE # no C++ files, prevent CMake error.
   )
 
diff --git a/flang/CMakeLists.txt b/flang/CMakeLists.txt
index c01eb56d5e496..e21304d2e4da7 100644
--- a/flang/CMakeLists.txt
+++ b/flang/CMakeLists.txt
@@ -441,11 +441,6 @@ if (LLVM_COMPILER_IS_GCC_COMPATIBLE)
   if (BUILD_SHARED_LIBS AND NOT (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-semantic-interposition")
   endif()
-
-  # GCC requires this flag in order for precompiled headers to work with ccache
-  if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT CMAKE_DISABLE_PRECOMPILE_HEADERS)
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fpch-preprocess")
-  endif()
 endif()
 
 # Clang on Darwin enables non-POSIX extensions by default, which allows the
@@ -456,11 +451,6 @@ if (APPLE)
   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_POSIX_C_SOURCE=200809")
 endif()
 
-# Clang requires this flag in order for precompiled headers to work with ccache
-if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT CMAKE_DISABLE_PRECOMPILE_HEADERS)
-  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xclang -fno-pch-timestamp")
-endif()
-
 list(REMOVE_DUPLICATES CMAKE_CXX_FLAGS)
 
 # Determine HOST_LINK_VERSION on Darwin.
diff --git a/flang/lib/Evaluate/CMakeLists.txt b/flang/lib/Evaluate/CMakeLists.txt
index 24a1c9004bc3b..472ecb6d8d079 100644
--- a/flang/lib/Evaluate/CMakeLists.txt
+++ b/flang/lib/Evaluate/CMakeLists.txt
@@ -66,15 +66,8 @@ add_flang_library(FortranEvaluate
   ${LIBPGMATH}
   ${QUADMATHLIB}
 
-  LINK_COMPONENTS
-  Support
-
-  DEPENDS
-  acc_gen
-  omp_gen
-)
-
-target_precompile_headers(FortranEvaluate PRIVATE
+  DISABLE_PCH_REUSE
+  PRECOMPILE_HEADERS
   [["flang/Evaluate/common.h"]]
   [["flang/Evaluate/call.h"]]
   [["flang/Evaluate/traverse.h"]]
@@ -86,4 +79,11 @@ target_precompile_headers(FortranEvaluate PRIVATE
   [["flang/Evaluate/integer.h"]]
   [["flang/Evaluate/expression.h"]]
   [["flang/Evaluate/tools.h"]]
+
+  LINK_COMPONENTS
+  Support
+
+  DEPENDS
+  acc_gen
+  omp_gen
 )
diff --git a/flang/lib/Frontend/CMakeLists.txt b/flang/lib/Frontend/CMakeLists.txt
index 4ebe497e65676..bd67ec444f358 100644
--- a/flang/lib/Frontend/CMakeLists.txt
+++ b/flang/lib/Frontend/CMakeLists.txt
@@ -13,6 +13,14 @@ add_flang_library(flangFrontend
   TextDiagnosticBuffer.cpp
   TextDiagnostic.cpp
 
+  DISABLE_PCH_REUSE
+  PRECOMPILE_HEADERS
+  [["flang/Parser/parsing.h"]]
+  [["flang/Parser/parse-tree.h"]]
+  [["flang/Parser/dump-parse-tree.h"]]
+  [["flang/Lower/PFTBuilder.h"]]
+  [["flang/Lower/Bridge.h"]]
+
   DEPENDS
   CUFDialect
   FIRDialect
@@ -78,11 +86,3 @@ add_flang_library(flangFrontend
   clangBasic
   clangOptions
 )
-
-target_precompile_headers(flangFrontend PRIVATE
-  [["flang/Parser/parsing.h"]]
-  [["flang/Parser/parse-tree.h"]]
-  [["flang/Parser/dump-parse-tree.h"]]
-  [["flang/Lower/PFTBuilder.h"]]
-  [["flang/Lower/Bridge.h"]]
-)
diff --git a/flang/lib/Lower/CMakeLists.txt b/flang/lib/Lower/CMakeLists.txt
index 230a56ab66ec5..f5424c78b9a98 100644
--- a/flang/lib/Lower/CMakeLists.txt
+++ b/flang/lib/Lower/CMakeLists.txt
@@ -38,6 +38,17 @@ add_flang_library(FortranLower
   Support/Utils.cpp
   SymbolMap.cpp
   VectorSubscripts.cpp
+
+  DISABLE_PCH_REUSE
+  PRECOMPILE_HEADERS
+  [["flang/Lower/ConvertExpr.h"]]
+  [["flang/Lower/SymbolMap.h"]]
+  [["flang/Lower/AbstractConverter.h"]]
+  [["flang/Lower/IterationSpace.h"]]
+  [["flang/Lower/CallInterface.h"]]
+  [["flang/Lower/BoxAnalyzer.h"]]
+  [["flang/Lower/PFTBuilder.h"]]
+  [["flang/Lower/DirectivesCommon.h"]]
   
   DEPENDS
   CUFAttrs
@@ -79,14 +90,3 @@ add_flang_library(FortranLower
   MLIRLLVMDialect
   MLIRSCFToControlFlow
 )
-
-target_precompile_headers(FortranLower PRIVATE
-  [["flang/Lower/ConvertExpr.h"]]
-  [["flang/Lower/SymbolMap.h"]]
-  [["flang/Lower/AbstractConverter.h"]]
-  [["flang/Lower/IterationSpace.h"]]
-  [["flang/Lower/CallInterface.h"]]
-  [["flang/Lower/BoxAnalyzer.h"]]
-  [["flang/Lower/PFTBuilder.h"]]
-  [["flang/Lower/DirectivesCommon.h"]]
-)
diff --git a/flang/lib/Parser/CMakeLists.txt b/flang/lib/Parser/CMakeLists.txt
index 20c6c2a7c8f80..e0479b0da3eb8 100644
--- a/flang/lib/Parser/CMakeLists.txt
+++ b/flang/lib/Parser/CMakeLists.txt
@@ -25,6 +25,14 @@ add_flang_library(FortranParser
   unparse.cpp
   user-state.cpp
 
+  DISABLE_PCH_REUSE
+  PRECOMPILE_HEADERS
+  [["flang/Parser/parsing.h"]]
+  [["flang/Parser/parse-tree.h"]]
+  [["flang/Parser/provenance.h"]]
+  [["flang/Parser/message.h"]]
+  [["flang/Parser/parse-tree-visitor.h"]]
+
   LINK_LIBS
   FortranSupport
 
@@ -37,11 +45,3 @@ add_flang_library(FortranParser
   omp_gen
   acc_gen
 )
-
-target_precompile_headers(FortranParser PRIVATE
-  [["flang/Parser/parsing.h"]]
-  [["flang/Parser/parse-tree.h"]]
-  [["flang/Parser/provenance.h"]]
-  [["flang/Parser/message.h"]]
-  [["flang/Parser/parse-tree-visitor.h"]]
-)
diff --git a/flang/lib/Semantics/CMakeLists.txt b/flang/lib/Semantics/CMakeLists.txt
index 109bc2dbb8569..44e6dfb4dd09f 100644
--- a/flang/lib/Semantics/CMakeLists.txt
+++ b/flang/lib/Semantics/CMakeLists.txt
@@ -53,6 +53,15 @@ add_flang_library(FortranSemantics
   type.cpp
   unparse-with-symbols.cpp
 
+  DISABLE_PCH_REUSE
+  PRECOMPILE_HEADERS
+  [["flang/Semantics/semantics.h"]]
+  [["flang/Semantics/type.h"]]
+  [["flang/Semantics/openmp-modifiers.h"]]
+  [["flang/Semantics/expression.h"]]
+  [["flang/Semantics/tools.h"]]
+  [["flang/Semantics/symbol.h"]]
+
   DEPENDS
   acc_gen
   omp_gen
@@ -68,12 +77,3 @@ add_flang_library(FortranSemantics
   FrontendOpenACC
   TargetParser
 )
-
-target_precompile_headers(FortranSemantics PRIVATE
-  [["flang/Semantics/semantics.h"]]
-  [["flang/Semantics/type.h"]]
-  [["flang/Semantics/openmp-modifiers.h"]]
-  [["flang/Semantics/expression.h"]]
-  [["flang/Semantics/tools.h"]]
-  [["flang/Semantics/symbol.h"]]
-)
diff --git a/llvm/CMakeLists.txt b/llvm/CMakeLists.txt
index 9954053591d30..a539a2182efa5 100644
--- a/llvm/CMakeLists.txt
+++ b/llvm/CMakeLists.txt
@@ -313,6 +313,22 @@ if(LLVM_CCACHE_BUILD)
         set(CCACHE_PROGRAM "CCACHE_DIR=${LLVM_CCACHE_DIR} ${CCACHE_PROGRAM}")
       endif()
       set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_PROGRAM})
+
+      if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+        # ccache with PCH can lead to false-positives when only a macro
+        # definition changes with non-Clang compilers, because macro definitions
+        # are not compared in preprocessed mode.
+        # See: https://github.com/ccache/ccache/issues/1668
+        if(NOT DEFINED CMAKE_DISABLE_PRECOMPILE_HEADERS)
+          message(NOTICE "Using ccache with precompiled headers with non-Clang "
+            "compilers is not supported. CMAKE_DISABLE_PRECOMPILE_HEADERS will be set to ON. "
+            "Pass -DCMAKE_DISABLE_PRECOMPILE_HEADERS=OFF to override this.")
+          set(CMAKE_DISABLE_PRECOMPILE_HEADERS "ON")
+        elseif(NOT CMAKE_DISABLE_PRECOMPILE_HEADERS)
+          message(WARNING "Using ccache with precompiled headers with non-Clang "
+            "compilers is not supported.")
+        endif()
+      endif()
     else()
       # Until a way to reliably configure ccache on Windows is found,
       # disable precompiled headers for Windows + ccache builds
diff --git a/llvm/cmake/modules/AddLLVM.cmake b/llvm/cmake/modules/AddLLVM.cmake
index d938214f9d0df..c9ed03d98cc9c 100644
--- a/llvm/cmake/modules/AddLLVM.cmake
+++ b/llvm/cmake/modules/AddLLVM.cmake
@@ -79,6 +79,48 @@ function(llvm_update_compile_flags name)
   target_compile_definitions(${name} PRIVATE ${LLVM_COMPILE_DEFINITIONS})
 endfunction()
 
+function(llvm_update_pch name)
+  if(LLVM_REQUIRES_RTTI OR LLVM_REQUIRES_EH)
+    # Non-default RTTI/EH results in incompatible flags, precluding PCH reuse.
+    set(ARG_DISABLE_PCH_REUSE ON)
+  endif()
+
+  # Find PCH with highest priority from dependencies. We reuse the first PCH
+  # with the highest priority. If the target has its own set of PCH, we give it
+  # a higher priority so that dependents will prefer the new PCH. We don't do
+  # transitive PCH reuse, because this causes too many unrelated naming
+  # collisions (e.g., in A -> B -> C{pch}, only B would reuse the PCH of C).
+  set(pch_priority 0)
+  llvm_map_components_to_libnames(libs
+    ${LLVM_LINK_COMPONENTS}
+  )
+  list(APPEND libs ${ARG_LINK_LIBS})
+  foreach(lib ${libs})
+    if(TARGET ${lib})
+      get_target_property(lib_pch_priority ${lib} LLVM_PCH_PRIORITY)
+      if(${lib_pch_priority} GREATER ${pch_priority})
+        set(pch_priority ${lib_pch_priority})
+        set(pch_reuse ${lib})
+      endif()
+    endif()
+  endforeach()
+
+  if(ARG_PRECOMPILE_HEADERS)
+    message(DEBUG "Adding PCH ${ARG_PRECOMPILE_HEADERS} for ${name} (prio ${pch_priority})")
+    target_precompile_headers(${name} PRIVATE $<$<COMPILE_LANGUAGE:CXX>:${ARG_PRECOMPILE_HEADERS}>)
+    if(NOT ARG_DISABLE_PCH_REUSE)
+      # Set priority so that dependants can reuse the PCH.
+      math(EXPR pch_priority "${pch_priority} + 1")
+      set_target_properties(${name} PROPERTIES LLVM_PCH_PRIORITY ${pch_priority})
+    endif()
+  elseif(pch_reuse AND NOT ARG_DISABLE_PCH_REUSE)
+    message(DEBUG "Using PCH ${pch_reuse} for ${name} (prio ${pch_priority})")
+    target_precompile_headers(${name} REUSE_FROM ${pch_reuse})
+  else()
+    message(DEBUG "Using NO PCH for ${name}")
+  endif()
+endfunction()
+
 function(add_llvm_symbol_exports target_name export_file)
   if("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin")
     set(native_export_file "${target_name}.exports")
@@ -485,6 +527,13 @@ endfunction(set_windows_version_resource_properties)
 #     Corresponds to OUTPUT_NAME in target properties.
 #   DEPENDS targets...
 #     Same semantics as add_dependencies().
+#   PRECOMPILE_HEADERS include_directives...
+#     Pre-compiled C++ headers to use. PCH can be reused by dependants. If
+#     specified, no PCHs from dependencies will be reused.
+#   DISABLE_PCH_REUSE
+#     Disable reuse of pre-compiled headers in both directions: the library will
+#     not reuse the PCH of a dependency and a defined PCH will not be offered
+#     for reuse by dependants.
 #   LINK_COMPONENTS components...
 #     Same as the variable LLVM_LINK_COMPONENTS.
 #   LINK_LIBS lib_targets...
@@ -504,11 +553,12 @@ endfunction(set_windows_version_resource_properties)
 #   )
 function(llvm_add_library name)
   cmake_parse_arguments(ARG
-    "MODULE;SHARED;STATIC;OBJECT;DISABLE_LLVM_LINK_LLVM_DYLIB;SONAME;NO_INSTALL_RPATH;COMPONENT_LIB"
+    "MODULE;SHARED;STATIC;OBJECT;DISABLE_LLVM_LINK_LLVM_DYLIB;SONAME;NO_INSTALL_RPATH;COMPONENT_LIB;DISABLE_PCH_REUSE"
     "OUTPUT_NAME;PLUGIN_TOOL;ENTITLEMENTS;BUNDLE_PATH"
-    "ADDITIONAL_HEADERS;DEPENDS;LINK_COMPONENTS;LINK_LIBS;OBJLIBS"
+    "ADDITIONAL_HEADERS;PRECOMPILE_HEADERS;DEPENDS;LINK_COMPONENTS;LINK_LIBS;OBJLIBS"
     ${ARGN})
   list(APPEND LLVM_COMMON_DEPENDS ${ARG_DEPENDS})
+  list(APPEND LLVM_LINK_COMPONENTS ${ARG_LINK_COMPONENTS})
   if(ARG_ADDITIONAL_HEADERS)
     # Pass through ADDITIONAL_HEADERS.
     set(ARG_ADDITIONAL_HEADERS ADDITIONAL_HEADERS ${ARG_ADDITIONAL_HEADERS})
@@ -550,6 +600,7 @@ function(llvm_add_library name)
       ${ALL_FILES}
       )
     llvm_update_compile_flags(${obj_name})
+    llvm_update_pch(${obj_name})
     if(CMAKE_GENERATOR STREQUAL "Xcode")
       set(DUMMY_FILE ${CMAKE_CURRENT_BINARY_DIR}/Dummy.c)
       file(WRITE ${DUMMY_FILE} "// This file intentionally empty\n")
@@ -604,7 +655,7 @@ function(llvm_add_library name)
       ${output_name}
       OBJLIBS ${ALL_FILES} # objlib
       LINK_LIBS ${ARG_LINK_LIBS}
-      LINK_COMPONENTS ${ARG_LINK_COMPONENTS}
+      LINK_COMPONENTS ${LLVM_LINK_COMPONENTS}
       )
     set_target_properties(${name_static} PROPERTIES FOLDER "${subproject_title}/Libraries")
 
@@ -676,6 +727,11 @@ function(llvm_add_library name)
   # $<TARGET_OBJECTS> doesn't require compile flags.
   if(NOT obj_name)
     llvm_update_compile_flags(${name})
+    llvm_update_pch(${name})
+  else()
+    target_precompile_headers(${name} REUSE_FROM ${obj_name})
+    get_target_property(pch_priority ${obj_name} LLVM_PCH_PRIORITY)
+    set_target_properties(${name} PROPERTIES LLVM_PCH_PRIORITY ${pch_priority})
   endif()
   add_link_opts( ${name} )
   if(ARG_OUTPUT_NAME)
@@ -758,8 +814,7 @@ function(llvm_add_library name)
         target_compile_definitions(${name} PRIVATE LLVM_BUILD_STATIC)
       endif()
       llvm_map_components_to_libnames(llvm_libs
-       ${ARG_LINK_COMPONENTS}
-       ${LLVM_LINK_COMPONENTS}
+        ${LLVM_LINK_COMPONENTS}
        )
     endif()
   else()
@@ -770,7 +825,7 @@ function(llvm_add_library name)
     # It would be nice to verify that we have the dependencies for this library
     # name, but using get_property(... SET) doesn't suffice to determine if a
     # property has been set to an empty value.
-    set_property(TARGET ${name} PROPERTY LLVM_LINK_COMPONENTS ${ARG_LINK_COMPONENTS} ${LLVM_LINK_COMPONENTS})
+    set_property(TARGET ${name} PROPERTY LLVM_LINK_COMPONENTS ${LLVM_LINK_COMPONENTS})
 
     # This property is an internal property only used to make sure the
     # link step applied in LLVMBuildResolveComponentsLink uses the same
@@ -993,6 +1048,7 @@ macro(generate_llvm_objects name)
       ${ALL_FILES}
       )
     llvm_update_compile_flags(${obj_name})
+    llvm_update_pch(${obj_name})
     set(ALL_FILES "$<TARGET_OBJECTS:${obj_name}>")
     if(ARG_DEPENDS)
       add_dependencies(${obj_name} ${ARG_DEPENDS})
@@ -1032,7 +1088,7 @@ endmacro()
 
 macro(add_llvm_executable name)
   cmake_parse_arguments(ARG
-    "DISABLE_LLVM_LINK_LLVM_DYLIB;IGNORE_EXTERNALIZE_DEBUGINFO;NO_INSTALL_RPATH;SUPPORT_PLUGINS;EXPORT_SYMBOLS"
+    "DISABLE_LLVM_LINK_LLVM_DYLIB;IGNORE_EXTERNALIZE_DEBUGINFO;NO_INSTALL_RPATH;SUPPORT_PLUGINS;EXPORT_SYMBOLS;DISABLE_PCH_REUSE"
     "ENTITLEMENTS;BUNDLE_PATH"
     ""
     ${ARGN})
@@ -1073,6 +1129,11 @@ macro(add_llvm_executable name)
   # $<TARGET_OBJECTS> doesn't require compile flags.
   if(NOT LLVM_ENABLE_OBJLIB)
     llvm_update_compile_flags(${name})
+    llvm_update_pch(${name})
+  else()
+    target_precompile_headers(${name} REUSE_FROM ${obj_name})
+    get_target_property(pch_priority ${obj_name} LLVM_PCH_PRIORITY)
+    set_target_properties(${name} PROPERTIES LLVM_PCH_PRIORITY ${pch_priority})
   endif()
 
   if (ARG_SUPPORT_PLUGINS AND NOT "${CMAKE_SYSTEM_NAME}" MATCHES "AIX")
@@ -1780,7 +1841,7 @@ function(add_unittest test_suite test_name)
   endif()
 
   list(APPEND LLVM_LINK_COMPONENTS Support) # gtest needs it for raw_ostream
-  add_llvm_executable(${test_name} IGNORE_EXTERNALIZE_DEBUGINFO NO_INSTALL_RPATH ${ARGN})
+  add_llvm_executable(${test_name} IGNORE_EXTERNALIZE_DEBUGINFO NO_INSTALL_RPATH DISABLE_PCH_REUSE ${ARGN})
   get_subproject_title(subproject_title)
   set_target_properties(${test_name} PROPERTIES FOLDER "${subproject_title}/Tests/Unit")
 
diff --git a/llvm/cmake/modules/HandleLLVMOptions.cmake b/llvm/cmake/modules/HandleLLVMOptions.cmake
index 311123084bf58..2e90c0ad0ad76 100644
--- a/llvm/cmake/modules/HandleLLVMOptions.cmake
+++ b/llvm/cmake/modules/HandleLLVMOptions.cmake
@@ -1304,6 +1304,27 @@ if (LLVM_BUILD_INSTRUMENTED AND LLVM_BUILD_INSTRUMENTED_COVERAGE)
   message(FATAL_ERROR "LLVM_BUILD_INSTRUMENTED and LLVM_BUILD_INSTRUMENTED_COVERAGE cannot both be specified")
 endif()
 
+if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT DEFINED CMAKE_DISABLE_PRECOMPILE_HEADERS)
+  # Pre-compiled headers with GCC (tested versions 14+15) provide very little
+  # compile-time improvements, but substantially increase the build dir size.
+  # Therefore, disable PCH with GCC by default.
+  message(NOTICE "Precompiled headers are disabled by default with GCC. "
+    "Pass -DCMAKE_DISABLE_PRECOMPILE_HEADERS=OFF to override.")
+  set(CMAKE_DISABLE_PRECOMPILE_HEADERS ON)
+endif()
+if(NOT CMAKE_DISABLE_PRECOMPILE_HEADERS)
+  message(STATUS "Precompiled headers enabled.")
+  if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+    # Clang requires this flag in order for precompiled headers to work with ccache
+    append("-Xclang -fno-pch-timestamp" CMAKE_CXX_FLAGS)
+  elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+    # GCC requires this flag in order for precompiled headers to work with ccache
+    append("-fpch-preprocess" CMAKE_CXX_FLAGS)
+  endif()
+else()
+  message(STATUS "Precompiled headers disabled.")
+endif()
+
 set(LLVM_THINLTO_CACHE_PATH "${PROJECT_BINARY_DIR}/lto.cache" CACHE STRING "Set ThinLTO cache path. This can be used when building LLVM from several different directiories.")
 
 if(LLVM_ENABLE_LTO AND WIN32 AND NOT LINKER_IS_LLD_LINK AND NOT MINGW)
diff --git a/llvm/examples/OrcV2Examples/OrcV2CBindingsAddObjectFile/CMakeLists.txt b/llvm/examples/OrcV2Examples/OrcV2CBindingsAddObjectFile/CMakeLists.txt
index cc50112f326ea..9a82cc0ed0916 100644
--- a/llvm/examples/OrcV2Examples/OrcV2CBindingsAddObjectFile/CMakeLists.txt
+++ b/llvm/examples/OrcV2Examples/OrcV2CBindingsAddObjectFile/CMakeLists.txt
@@ -12,4 +12,6 @@ set(LLVM_LINK_COMPONENTS
 
 add_llvm_example(OrcV2CBindingsAddObjectFile
   OrcV2CBindingsAddObjectFile.c
+
+  DISABLE_PCH_REUSE # no C++ files, prevent CMake error.
   )
diff --git a/llvm/examples/OrcV2Examples/OrcV2CBindingsBasicUsage/CMakeLists.txt b/llvm/examples/OrcV2Examples/OrcV2CBindingsBasicUsage/CMakeLists.txt
index 0f18d6c24912f..1a738f60d15e1 100644
--- a/llvm/examples/OrcV2Examples/OrcV2CBindingsBasicUsage/CMakeLists.txt
+++ b/llvm/examples/OrcV2Examples/OrcV2CBindingsBasicUsage/CMakeLists.txt
@@ -12,4 +12,6 @@ set(LLVM_LINK_COMPONENTS
 
 add_llvm_example(OrcV2CBindingsBasicUsage
   OrcV2CBindingsBasicUsage.c
+
+  DISABLE_PCH_REUSE # no C++ files, prevent CMake error.
   )
diff --git a/llvm/examples/OrcV2Examples/OrcV2CBindingsDumpObjects/CMakeLists.txt b/llvm/examples/OrcV2Examples/OrcV2CBindingsDumpObjects/CMakeLists.txt
index 8e2c97d782062..c7f748a12bf35 100644
--- a/llvm/examples/OrcV2Examples/OrcV2CBindingsDumpObjects/CMakeLists.txt
+++ b/llvm/examples/OrcV2Examples/OrcV2CBindingsDumpObjects/CMakeLists.txt
@@ -12,4 +12,6 @@ set(LLVM_LINK_COMPONENTS
 
 add_llvm_example(OrcV2CBindingsDumpObjects
   OrcV2CBindingsDumpObjects.c
+
+  DISABLE_PCH_REUSE # no C++ files, prevent CMake error.
   )
diff --git a/llvm/examples/OrcV2Examples/OrcV2CBindingsIRTransforms/CMakeLists.txt b/llvm/examples/OrcV2Examples/OrcV2CBindingsIRTransforms/CMakeLists.txt
index af1b43e43e111..b152faf1e3527 100644
--- a/llvm/examples/OrcV2Examples/OrcV2CBindingsIRTransforms/CMakeLists.txt
+++ b/llvm/examples/OrcV2Examples/OrcV2CBindingsIRTransforms/CMakeLists.txt
@@ -13,4 +13,6 @@ set(LLVM_LINK_COMPONENTS
 
 add_llvm_example(OrcV2CBindingsIRTransforms
   OrcV2CBindingsIRTransforms.c
+
+  DISABLE_PCH_REUSE # no C++ files, prevent CMake error.
   )
diff --git a/llvm/examples/OrcV2Examples/OrcV2CBindingsLazy/CMakeLists.txt b/llvm/examples/OrcV2Examples/OrcV2CBindingsLazy/CMakeLists.txt
index 52eb2d496fc23..c46700d9b2576 100644
--- a/llvm/examples/OrcV2Examples/OrcV2CBindingsLazy/CMakeLists.txt
+++ b/llvm/examples/OrcV2Examples/OrcV2CBindingsLazy/CMakeLists.txt
@@ -12,4 +12,6 @@ set(LLVM_LINK_COMPONENTS
 
 add_llvm_example(OrcV2CBindingsLazy
   OrcV2CBindingsLazy.c
+
+  DISABLE_PCH_REUSE # no C++ files, prevent CMake error.
   )
diff --git a/llvm/examples/OrcV2Examples/OrcV2CBindingsMCJITLikeMemoryManager/CMakeLists.txt b/llvm/examples/OrcV2Examples/OrcV2CBindingsMCJITLikeMemoryManager/CMakeLists.txt
index 4cdc2b114e9a5..d4a06104120c8 100644
--- a/llvm/examples/OrcV2Examples/OrcV2CBindingsMCJITLikeMemoryManager/CMakeLists.txt
+++ b/llvm/examples/OrcV2Examples/OrcV2CBindingsMCJITLikeMemoryManager/CMakeLists.txt
@@ -12,4 +12,6 @@ set(LLVM_LINK_COMPONENTS
 
 add_llvm_exampl...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Jan 16, 2026

@llvm/pr-subscribers-llvm-support

Author: Alexis Engelke (aengelke)

Changes

This patch implements PCH support.

  • Libraries can defined precompiled headers using a newly added PRECOMPILE_HEADERS keyword. If specified, the listed headers will be compiled into a pre-compiled header using standard CMake mechanisms.

  • Libraries that don't define their own PRECOMPILE_HEADERS but directly depend on a library or component that defines its own PCH will reuse that PCH. This reuse is not transitive to prevent excessive use of unrelated headers. If multiple dependencies provide a reusable PCH, the first one with the longest dependency chain (stored in the CMake target property LLVM_PCH_PRIORITY) is used. However, due to CMake limitations, only PCH from targets that are already defined can be reused; therefore libraries that should reuse a PCH must be defined later in the CMake file (=> add_subdirectory order matters).

  • Libraries and executables can prevent PCH reuse with the keyword DISABLE_PCH_REUSE. This both prevents reuse from dependencies and reuse by other dependants. This is useful when, e.g., internal headers are used in the PCH or the used headers are unlikely to provide benefits for dependants.

  • Precompiled headers are only used for C++ targets. Due to CMake-weirdness, C-only targets must explicitly disable PCH reuse, otherwise configuration fails.

  • With GCC, PCH provide very little benefits (tested with GCC 14 and 15) due to increased template instatiation costs, but substantially increase max-rss and build directory size. Therefore, disable PCH with GCC by default; this can be explicitly overridden on the command line with -DCMAKE_DISABLE_PRECOMPILE_HEADERS=OFF.

  • With ccache and non-Clang compilers, changes in macro definitions are not always accurately forwarded with ccache's preprocessed mode. To be on the safe side, when ccache is enabled, disable PCH with all non-Clang compilers; this can be explicitly overridden.

  • Add a base PCH to LLVMSupport, which includes widely used standard library and Support+ADT headers. The pch.h is placed in include so that later PCH headers can extend that list of headers.

  • Flang PCH use is ported to the general mechanism.

Addition of PCH headers for other components (e.g., IR, CodeGen) will be posted as separate PRs.

RFC: https://discourse.llvm.org/t/rfc-use-pre-compiled-headers-to-speed-up-llvm-build-by-1-5-2x/89345


Patch is 25.98 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/176420.diff

25 Files Affected:

  • (modified) clang/tools/c-index-test/CMakeLists.txt (+2)
  • (modified) clang/tools/clang-fuzzer/dictionary/CMakeLists.txt (+2)
  • (modified) flang/CMakeLists.txt (-10)
  • (modified) flang/lib/Evaluate/CMakeLists.txt (+9-9)
  • (modified) flang/lib/Frontend/CMakeLists.txt (+8-8)
  • (modified) flang/lib/Lower/CMakeLists.txt (+11-11)
  • (modified) flang/lib/Parser/CMakeLists.txt (+8-8)
  • (modified) flang/lib/Semantics/CMakeLists.txt (+9-9)
  • (modified) llvm/CMakeLists.txt (+16)
  • (modified) llvm/cmake/modules/AddLLVM.cmake (+69-8)
  • (modified) llvm/cmake/modules/HandleLLVMOptions.cmake (+21)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsAddObjectFile/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsBasicUsage/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsDumpObjects/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsIRTransforms/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsLazy/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsMCJITLikeMemoryManager/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsRemovableCode/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsVeryLazy/CMakeLists.txt (+2)
  • (added) llvm/include/llvm/Support/pch.h (+75)
  • (modified) llvm/lib/CMakeLists.txt (+1-1)
  • (modified) llvm/lib/Support/CMakeLists.txt (+6-1)
  • (modified) llvm/tools/llvm-c-test/CMakeLists.txt (+2)
  • (modified) llvm/utils/count/CMakeLists.txt (+2)
  • (modified) mlir/test/CAPI/CMakeLists.txt (+1)
diff --git a/clang/tools/c-index-test/CMakeLists.txt b/clang/tools/c-index-test/CMakeLists.txt
index 41e80e66ffa7a..2bbd387d6e812 100644
--- a/clang/tools/c-index-test/CMakeLists.txt
+++ b/clang/tools/c-index-test/CMakeLists.txt
@@ -5,6 +5,8 @@ set(LLVM_LINK_COMPONENTS
 add_clang_executable(c-index-test
   c-index-test.c
   core_main.cpp
+
+  DISABLE_PCH_REUSE # Prevent CMake error with C source files.
   )
 
 if(NOT MSVC)
diff --git a/clang/tools/clang-fuzzer/dictionary/CMakeLists.txt b/clang/tools/clang-fuzzer/dictionary/CMakeLists.txt
index 6b72b98f5e1c4..a7f18965b4e29 100644
--- a/clang/tools/clang-fuzzer/dictionary/CMakeLists.txt
+++ b/clang/tools/clang-fuzzer/dictionary/CMakeLists.txt
@@ -1,5 +1,7 @@
 set(CMAKE_CXX_FLAGS ${CXX_FLAGS_NOFUZZ})
 add_clang_executable(clang-fuzzer-dictionary
   dictionary.c
+
+  DISABLE_PCH_REUSE # no C++ files, prevent CMake error.
   )
 
diff --git a/flang/CMakeLists.txt b/flang/CMakeLists.txt
index c01eb56d5e496..e21304d2e4da7 100644
--- a/flang/CMakeLists.txt
+++ b/flang/CMakeLists.txt
@@ -441,11 +441,6 @@ if (LLVM_COMPILER_IS_GCC_COMPATIBLE)
   if (BUILD_SHARED_LIBS AND NOT (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-semantic-interposition")
   endif()
-
-  # GCC requires this flag in order for precompiled headers to work with ccache
-  if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT CMAKE_DISABLE_PRECOMPILE_HEADERS)
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fpch-preprocess")
-  endif()
 endif()
 
 # Clang on Darwin enables non-POSIX extensions by default, which allows the
@@ -456,11 +451,6 @@ if (APPLE)
   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_POSIX_C_SOURCE=200809")
 endif()
 
-# Clang requires this flag in order for precompiled headers to work with ccache
-if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT CMAKE_DISABLE_PRECOMPILE_HEADERS)
-  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xclang -fno-pch-timestamp")
-endif()
-
 list(REMOVE_DUPLICATES CMAKE_CXX_FLAGS)
 
 # Determine HOST_LINK_VERSION on Darwin.
diff --git a/flang/lib/Evaluate/CMakeLists.txt b/flang/lib/Evaluate/CMakeLists.txt
index 24a1c9004bc3b..472ecb6d8d079 100644
--- a/flang/lib/Evaluate/CMakeLists.txt
+++ b/flang/lib/Evaluate/CMakeLists.txt
@@ -66,15 +66,8 @@ add_flang_library(FortranEvaluate
   ${LIBPGMATH}
   ${QUADMATHLIB}
 
-  LINK_COMPONENTS
-  Support
-
-  DEPENDS
-  acc_gen
-  omp_gen
-)
-
-target_precompile_headers(FortranEvaluate PRIVATE
+  DISABLE_PCH_REUSE
+  PRECOMPILE_HEADERS
   [["flang/Evaluate/common.h"]]
   [["flang/Evaluate/call.h"]]
   [["flang/Evaluate/traverse.h"]]
@@ -86,4 +79,11 @@ target_precompile_headers(FortranEvaluate PRIVATE
   [["flang/Evaluate/integer.h"]]
   [["flang/Evaluate/expression.h"]]
   [["flang/Evaluate/tools.h"]]
+
+  LINK_COMPONENTS
+  Support
+
+  DEPENDS
+  acc_gen
+  omp_gen
 )
diff --git a/flang/lib/Frontend/CMakeLists.txt b/flang/lib/Frontend/CMakeLists.txt
index 4ebe497e65676..bd67ec444f358 100644
--- a/flang/lib/Frontend/CMakeLists.txt
+++ b/flang/lib/Frontend/CMakeLists.txt
@@ -13,6 +13,14 @@ add_flang_library(flangFrontend
   TextDiagnosticBuffer.cpp
   TextDiagnostic.cpp
 
+  DISABLE_PCH_REUSE
+  PRECOMPILE_HEADERS
+  [["flang/Parser/parsing.h"]]
+  [["flang/Parser/parse-tree.h"]]
+  [["flang/Parser/dump-parse-tree.h"]]
+  [["flang/Lower/PFTBuilder.h"]]
+  [["flang/Lower/Bridge.h"]]
+
   DEPENDS
   CUFDialect
   FIRDialect
@@ -78,11 +86,3 @@ add_flang_library(flangFrontend
   clangBasic
   clangOptions
 )
-
-target_precompile_headers(flangFrontend PRIVATE
-  [["flang/Parser/parsing.h"]]
-  [["flang/Parser/parse-tree.h"]]
-  [["flang/Parser/dump-parse-tree.h"]]
-  [["flang/Lower/PFTBuilder.h"]]
-  [["flang/Lower/Bridge.h"]]
-)
diff --git a/flang/lib/Lower/CMakeLists.txt b/flang/lib/Lower/CMakeLists.txt
index 230a56ab66ec5..f5424c78b9a98 100644
--- a/flang/lib/Lower/CMakeLists.txt
+++ b/flang/lib/Lower/CMakeLists.txt
@@ -38,6 +38,17 @@ add_flang_library(FortranLower
   Support/Utils.cpp
   SymbolMap.cpp
   VectorSubscripts.cpp
+
+  DISABLE_PCH_REUSE
+  PRECOMPILE_HEADERS
+  [["flang/Lower/ConvertExpr.h"]]
+  [["flang/Lower/SymbolMap.h"]]
+  [["flang/Lower/AbstractConverter.h"]]
+  [["flang/Lower/IterationSpace.h"]]
+  [["flang/Lower/CallInterface.h"]]
+  [["flang/Lower/BoxAnalyzer.h"]]
+  [["flang/Lower/PFTBuilder.h"]]
+  [["flang/Lower/DirectivesCommon.h"]]
   
   DEPENDS
   CUFAttrs
@@ -79,14 +90,3 @@ add_flang_library(FortranLower
   MLIRLLVMDialect
   MLIRSCFToControlFlow
 )
-
-target_precompile_headers(FortranLower PRIVATE
-  [["flang/Lower/ConvertExpr.h"]]
-  [["flang/Lower/SymbolMap.h"]]
-  [["flang/Lower/AbstractConverter.h"]]
-  [["flang/Lower/IterationSpace.h"]]
-  [["flang/Lower/CallInterface.h"]]
-  [["flang/Lower/BoxAnalyzer.h"]]
-  [["flang/Lower/PFTBuilder.h"]]
-  [["flang/Lower/DirectivesCommon.h"]]
-)
diff --git a/flang/lib/Parser/CMakeLists.txt b/flang/lib/Parser/CMakeLists.txt
index 20c6c2a7c8f80..e0479b0da3eb8 100644
--- a/flang/lib/Parser/CMakeLists.txt
+++ b/flang/lib/Parser/CMakeLists.txt
@@ -25,6 +25,14 @@ add_flang_library(FortranParser
   unparse.cpp
   user-state.cpp
 
+  DISABLE_PCH_REUSE
+  PRECOMPILE_HEADERS
+  [["flang/Parser/parsing.h"]]
+  [["flang/Parser/parse-tree.h"]]
+  [["flang/Parser/provenance.h"]]
+  [["flang/Parser/message.h"]]
+  [["flang/Parser/parse-tree-visitor.h"]]
+
   LINK_LIBS
   FortranSupport
 
@@ -37,11 +45,3 @@ add_flang_library(FortranParser
   omp_gen
   acc_gen
 )
-
-target_precompile_headers(FortranParser PRIVATE
-  [["flang/Parser/parsing.h"]]
-  [["flang/Parser/parse-tree.h"]]
-  [["flang/Parser/provenance.h"]]
-  [["flang/Parser/message.h"]]
-  [["flang/Parser/parse-tree-visitor.h"]]
-)
diff --git a/flang/lib/Semantics/CMakeLists.txt b/flang/lib/Semantics/CMakeLists.txt
index 109bc2dbb8569..44e6dfb4dd09f 100644
--- a/flang/lib/Semantics/CMakeLists.txt
+++ b/flang/lib/Semantics/CMakeLists.txt
@@ -53,6 +53,15 @@ add_flang_library(FortranSemantics
   type.cpp
   unparse-with-symbols.cpp
 
+  DISABLE_PCH_REUSE
+  PRECOMPILE_HEADERS
+  [["flang/Semantics/semantics.h"]]
+  [["flang/Semantics/type.h"]]
+  [["flang/Semantics/openmp-modifiers.h"]]
+  [["flang/Semantics/expression.h"]]
+  [["flang/Semantics/tools.h"]]
+  [["flang/Semantics/symbol.h"]]
+
   DEPENDS
   acc_gen
   omp_gen
@@ -68,12 +77,3 @@ add_flang_library(FortranSemantics
   FrontendOpenACC
   TargetParser
 )
-
-target_precompile_headers(FortranSemantics PRIVATE
-  [["flang/Semantics/semantics.h"]]
-  [["flang/Semantics/type.h"]]
-  [["flang/Semantics/openmp-modifiers.h"]]
-  [["flang/Semantics/expression.h"]]
-  [["flang/Semantics/tools.h"]]
-  [["flang/Semantics/symbol.h"]]
-)
diff --git a/llvm/CMakeLists.txt b/llvm/CMakeLists.txt
index 9954053591d30..a539a2182efa5 100644
--- a/llvm/CMakeLists.txt
+++ b/llvm/CMakeLists.txt
@@ -313,6 +313,22 @@ if(LLVM_CCACHE_BUILD)
         set(CCACHE_PROGRAM "CCACHE_DIR=${LLVM_CCACHE_DIR} ${CCACHE_PROGRAM}")
       endif()
       set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_PROGRAM})
+
+      if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+        # ccache with PCH can lead to false-positives when only a macro
+        # definition changes with non-Clang compilers, because macro definitions
+        # are not compared in preprocessed mode.
+        # See: https://github.com/ccache/ccache/issues/1668
+        if(NOT DEFINED CMAKE_DISABLE_PRECOMPILE_HEADERS)
+          message(NOTICE "Using ccache with precompiled headers with non-Clang "
+            "compilers is not supported. CMAKE_DISABLE_PRECOMPILE_HEADERS will be set to ON. "
+            "Pass -DCMAKE_DISABLE_PRECOMPILE_HEADERS=OFF to override this.")
+          set(CMAKE_DISABLE_PRECOMPILE_HEADERS "ON")
+        elseif(NOT CMAKE_DISABLE_PRECOMPILE_HEADERS)
+          message(WARNING "Using ccache with precompiled headers with non-Clang "
+            "compilers is not supported.")
+        endif()
+      endif()
     else()
       # Until a way to reliably configure ccache on Windows is found,
       # disable precompiled headers for Windows + ccache builds
diff --git a/llvm/cmake/modules/AddLLVM.cmake b/llvm/cmake/modules/AddLLVM.cmake
index d938214f9d0df..c9ed03d98cc9c 100644
--- a/llvm/cmake/modules/AddLLVM.cmake
+++ b/llvm/cmake/modules/AddLLVM.cmake
@@ -79,6 +79,48 @@ function(llvm_update_compile_flags name)
   target_compile_definitions(${name} PRIVATE ${LLVM_COMPILE_DEFINITIONS})
 endfunction()
 
+function(llvm_update_pch name)
+  if(LLVM_REQUIRES_RTTI OR LLVM_REQUIRES_EH)
+    # Non-default RTTI/EH results in incompatible flags, precluding PCH reuse.
+    set(ARG_DISABLE_PCH_REUSE ON)
+  endif()
+
+  # Find PCH with highest priority from dependencies. We reuse the first PCH
+  # with the highest priority. If the target has its own set of PCH, we give it
+  # a higher priority so that dependents will prefer the new PCH. We don't do
+  # transitive PCH reuse, because this causes too many unrelated naming
+  # collisions (e.g., in A -> B -> C{pch}, only B would reuse the PCH of C).
+  set(pch_priority 0)
+  llvm_map_components_to_libnames(libs
+    ${LLVM_LINK_COMPONENTS}
+  )
+  list(APPEND libs ${ARG_LINK_LIBS})
+  foreach(lib ${libs})
+    if(TARGET ${lib})
+      get_target_property(lib_pch_priority ${lib} LLVM_PCH_PRIORITY)
+      if(${lib_pch_priority} GREATER ${pch_priority})
+        set(pch_priority ${lib_pch_priority})
+        set(pch_reuse ${lib})
+      endif()
+    endif()
+  endforeach()
+
+  if(ARG_PRECOMPILE_HEADERS)
+    message(DEBUG "Adding PCH ${ARG_PRECOMPILE_HEADERS} for ${name} (prio ${pch_priority})")
+    target_precompile_headers(${name} PRIVATE $<$<COMPILE_LANGUAGE:CXX>:${ARG_PRECOMPILE_HEADERS}>)
+    if(NOT ARG_DISABLE_PCH_REUSE)
+      # Set priority so that dependants can reuse the PCH.
+      math(EXPR pch_priority "${pch_priority} + 1")
+      set_target_properties(${name} PROPERTIES LLVM_PCH_PRIORITY ${pch_priority})
+    endif()
+  elseif(pch_reuse AND NOT ARG_DISABLE_PCH_REUSE)
+    message(DEBUG "Using PCH ${pch_reuse} for ${name} (prio ${pch_priority})")
+    target_precompile_headers(${name} REUSE_FROM ${pch_reuse})
+  else()
+    message(DEBUG "Using NO PCH for ${name}")
+  endif()
+endfunction()
+
 function(add_llvm_symbol_exports target_name export_file)
   if("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin")
     set(native_export_file "${target_name}.exports")
@@ -485,6 +527,13 @@ endfunction(set_windows_version_resource_properties)
 #     Corresponds to OUTPUT_NAME in target properties.
 #   DEPENDS targets...
 #     Same semantics as add_dependencies().
+#   PRECOMPILE_HEADERS include_directives...
+#     Pre-compiled C++ headers to use. PCH can be reused by dependants. If
+#     specified, no PCHs from dependencies will be reused.
+#   DISABLE_PCH_REUSE
+#     Disable reuse of pre-compiled headers in both directions: the library will
+#     not reuse the PCH of a dependency and a defined PCH will not be offered
+#     for reuse by dependants.
 #   LINK_COMPONENTS components...
 #     Same as the variable LLVM_LINK_COMPONENTS.
 #   LINK_LIBS lib_targets...
@@ -504,11 +553,12 @@ endfunction(set_windows_version_resource_properties)
 #   )
 function(llvm_add_library name)
   cmake_parse_arguments(ARG
-    "MODULE;SHARED;STATIC;OBJECT;DISABLE_LLVM_LINK_LLVM_DYLIB;SONAME;NO_INSTALL_RPATH;COMPONENT_LIB"
+    "MODULE;SHARED;STATIC;OBJECT;DISABLE_LLVM_LINK_LLVM_DYLIB;SONAME;NO_INSTALL_RPATH;COMPONENT_LIB;DISABLE_PCH_REUSE"
     "OUTPUT_NAME;PLUGIN_TOOL;ENTITLEMENTS;BUNDLE_PATH"
-    "ADDITIONAL_HEADERS;DEPENDS;LINK_COMPONENTS;LINK_LIBS;OBJLIBS"
+    "ADDITIONAL_HEADERS;PRECOMPILE_HEADERS;DEPENDS;LINK_COMPONENTS;LINK_LIBS;OBJLIBS"
     ${ARGN})
   list(APPEND LLVM_COMMON_DEPENDS ${ARG_DEPENDS})
+  list(APPEND LLVM_LINK_COMPONENTS ${ARG_LINK_COMPONENTS})
   if(ARG_ADDITIONAL_HEADERS)
     # Pass through ADDITIONAL_HEADERS.
     set(ARG_ADDITIONAL_HEADERS ADDITIONAL_HEADERS ${ARG_ADDITIONAL_HEADERS})
@@ -550,6 +600,7 @@ function(llvm_add_library name)
       ${ALL_FILES}
       )
     llvm_update_compile_flags(${obj_name})
+    llvm_update_pch(${obj_name})
     if(CMAKE_GENERATOR STREQUAL "Xcode")
       set(DUMMY_FILE ${CMAKE_CURRENT_BINARY_DIR}/Dummy.c)
       file(WRITE ${DUMMY_FILE} "// This file intentionally empty\n")
@@ -604,7 +655,7 @@ function(llvm_add_library name)
       ${output_name}
       OBJLIBS ${ALL_FILES} # objlib
       LINK_LIBS ${ARG_LINK_LIBS}
-      LINK_COMPONENTS ${ARG_LINK_COMPONENTS}
+      LINK_COMPONENTS ${LLVM_LINK_COMPONENTS}
       )
     set_target_properties(${name_static} PROPERTIES FOLDER "${subproject_title}/Libraries")
 
@@ -676,6 +727,11 @@ function(llvm_add_library name)
   # $<TARGET_OBJECTS> doesn't require compile flags.
   if(NOT obj_name)
     llvm_update_compile_flags(${name})
+    llvm_update_pch(${name})
+  else()
+    target_precompile_headers(${name} REUSE_FROM ${obj_name})
+    get_target_property(pch_priority ${obj_name} LLVM_PCH_PRIORITY)
+    set_target_properties(${name} PROPERTIES LLVM_PCH_PRIORITY ${pch_priority})
   endif()
   add_link_opts( ${name} )
   if(ARG_OUTPUT_NAME)
@@ -758,8 +814,7 @@ function(llvm_add_library name)
         target_compile_definitions(${name} PRIVATE LLVM_BUILD_STATIC)
       endif()
       llvm_map_components_to_libnames(llvm_libs
-       ${ARG_LINK_COMPONENTS}
-       ${LLVM_LINK_COMPONENTS}
+        ${LLVM_LINK_COMPONENTS}
        )
     endif()
   else()
@@ -770,7 +825,7 @@ function(llvm_add_library name)
     # It would be nice to verify that we have the dependencies for this library
     # name, but using get_property(... SET) doesn't suffice to determine if a
     # property has been set to an empty value.
-    set_property(TARGET ${name} PROPERTY LLVM_LINK_COMPONENTS ${ARG_LINK_COMPONENTS} ${LLVM_LINK_COMPONENTS})
+    set_property(TARGET ${name} PROPERTY LLVM_LINK_COMPONENTS ${LLVM_LINK_COMPONENTS})
 
     # This property is an internal property only used to make sure the
     # link step applied in LLVMBuildResolveComponentsLink uses the same
@@ -993,6 +1048,7 @@ macro(generate_llvm_objects name)
       ${ALL_FILES}
       )
     llvm_update_compile_flags(${obj_name})
+    llvm_update_pch(${obj_name})
     set(ALL_FILES "$<TARGET_OBJECTS:${obj_name}>")
     if(ARG_DEPENDS)
       add_dependencies(${obj_name} ${ARG_DEPENDS})
@@ -1032,7 +1088,7 @@ endmacro()
 
 macro(add_llvm_executable name)
   cmake_parse_arguments(ARG
-    "DISABLE_LLVM_LINK_LLVM_DYLIB;IGNORE_EXTERNALIZE_DEBUGINFO;NO_INSTALL_RPATH;SUPPORT_PLUGINS;EXPORT_SYMBOLS"
+    "DISABLE_LLVM_LINK_LLVM_DYLIB;IGNORE_EXTERNALIZE_DEBUGINFO;NO_INSTALL_RPATH;SUPPORT_PLUGINS;EXPORT_SYMBOLS;DISABLE_PCH_REUSE"
     "ENTITLEMENTS;BUNDLE_PATH"
     ""
     ${ARGN})
@@ -1073,6 +1129,11 @@ macro(add_llvm_executable name)
   # $<TARGET_OBJECTS> doesn't require compile flags.
   if(NOT LLVM_ENABLE_OBJLIB)
     llvm_update_compile_flags(${name})
+    llvm_update_pch(${name})
+  else()
+    target_precompile_headers(${name} REUSE_FROM ${obj_name})
+    get_target_property(pch_priority ${obj_name} LLVM_PCH_PRIORITY)
+    set_target_properties(${name} PROPERTIES LLVM_PCH_PRIORITY ${pch_priority})
   endif()
 
   if (ARG_SUPPORT_PLUGINS AND NOT "${CMAKE_SYSTEM_NAME}" MATCHES "AIX")
@@ -1780,7 +1841,7 @@ function(add_unittest test_suite test_name)
   endif()
 
   list(APPEND LLVM_LINK_COMPONENTS Support) # gtest needs it for raw_ostream
-  add_llvm_executable(${test_name} IGNORE_EXTERNALIZE_DEBUGINFO NO_INSTALL_RPATH ${ARGN})
+  add_llvm_executable(${test_name} IGNORE_EXTERNALIZE_DEBUGINFO NO_INSTALL_RPATH DISABLE_PCH_REUSE ${ARGN})
   get_subproject_title(subproject_title)
   set_target_properties(${test_name} PROPERTIES FOLDER "${subproject_title}/Tests/Unit")
 
diff --git a/llvm/cmake/modules/HandleLLVMOptions.cmake b/llvm/cmake/modules/HandleLLVMOptions.cmake
index 311123084bf58..2e90c0ad0ad76 100644
--- a/llvm/cmake/modules/HandleLLVMOptions.cmake
+++ b/llvm/cmake/modules/HandleLLVMOptions.cmake
@@ -1304,6 +1304,27 @@ if (LLVM_BUILD_INSTRUMENTED AND LLVM_BUILD_INSTRUMENTED_COVERAGE)
   message(FATAL_ERROR "LLVM_BUILD_INSTRUMENTED and LLVM_BUILD_INSTRUMENTED_COVERAGE cannot both be specified")
 endif()
 
+if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT DEFINED CMAKE_DISABLE_PRECOMPILE_HEADERS)
+  # Pre-compiled headers with GCC (tested versions 14+15) provide very little
+  # compile-time improvements, but substantially increase the build dir size.
+  # Therefore, disable PCH with GCC by default.
+  message(NOTICE "Precompiled headers are disabled by default with GCC. "
+    "Pass -DCMAKE_DISABLE_PRECOMPILE_HEADERS=OFF to override.")
+  set(CMAKE_DISABLE_PRECOMPILE_HEADERS ON)
+endif()
+if(NOT CMAKE_DISABLE_PRECOMPILE_HEADERS)
+  message(STATUS "Precompiled headers enabled.")
+  if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+    # Clang requires this flag in order for precompiled headers to work with ccache
+    append("-Xclang -fno-pch-timestamp" CMAKE_CXX_FLAGS)
+  elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+    # GCC requires this flag in order for precompiled headers to work with ccache
+    append("-fpch-preprocess" CMAKE_CXX_FLAGS)
+  endif()
+else()
+  message(STATUS "Precompiled headers disabled.")
+endif()
+
 set(LLVM_THINLTO_CACHE_PATH "${PROJECT_BINARY_DIR}/lto.cache" CACHE STRING "Set ThinLTO cache path. This can be used when building LLVM from several different directiories.")
 
 if(LLVM_ENABLE_LTO AND WIN32 AND NOT LINKER_IS_LLD_LINK AND NOT MINGW)
diff --git a/llvm/examples/OrcV2Examples/OrcV2CBindingsAddObjectFile/CMakeLists.txt b/llvm/examples/OrcV2Examples/OrcV2CBindingsAddObjectFile/CMakeLists.txt
index cc50112f326ea..9a82cc0ed0916 100644
--- a/llvm/examples/OrcV2Examples/OrcV2CBindingsAddObjectFile/CMakeLists.txt
+++ b/llvm/examples/OrcV2Examples/OrcV2CBindingsAddObjectFile/CMakeLists.txt
@@ -12,4 +12,6 @@ set(LLVM_LINK_COMPONENTS
 
 add_llvm_example(OrcV2CBindingsAddObjectFile
   OrcV2CBindingsAddObjectFile.c
+
+  DISABLE_PCH_REUSE # no C++ files, prevent CMake error.
   )
diff --git a/llvm/examples/OrcV2Examples/OrcV2CBindingsBasicUsage/CMakeLists.txt b/llvm/examples/OrcV2Examples/OrcV2CBindingsBasicUsage/CMakeLists.txt
index 0f18d6c24912f..1a738f60d15e1 100644
--- a/llvm/examples/OrcV2Examples/OrcV2CBindingsBasicUsage/CMakeLists.txt
+++ b/llvm/examples/OrcV2Examples/OrcV2CBindingsBasicUsage/CMakeLists.txt
@@ -12,4 +12,6 @@ set(LLVM_LINK_COMPONENTS
 
 add_llvm_example(OrcV2CBindingsBasicUsage
   OrcV2CBindingsBasicUsage.c
+
+  DISABLE_PCH_REUSE # no C++ files, prevent CMake error.
   )
diff --git a/llvm/examples/OrcV2Examples/OrcV2CBindingsDumpObjects/CMakeLists.txt b/llvm/examples/OrcV2Examples/OrcV2CBindingsDumpObjects/CMakeLists.txt
index 8e2c97d782062..c7f748a12bf35 100644
--- a/llvm/examples/OrcV2Examples/OrcV2CBindingsDumpObjects/CMakeLists.txt
+++ b/llvm/examples/OrcV2Examples/OrcV2CBindingsDumpObjects/CMakeLists.txt
@@ -12,4 +12,6 @@ set(LLVM_LINK_COMPONENTS
 
 add_llvm_example(OrcV2CBindingsDumpObjects
   OrcV2CBindingsDumpObjects.c
+
+  DISABLE_PCH_REUSE # no C++ files, prevent CMake error.
   )
diff --git a/llvm/examples/OrcV2Examples/OrcV2CBindingsIRTransforms/CMakeLists.txt b/llvm/examples/OrcV2Examples/OrcV2CBindingsIRTransforms/CMakeLists.txt
index af1b43e43e111..b152faf1e3527 100644
--- a/llvm/examples/OrcV2Examples/OrcV2CBindingsIRTransforms/CMakeLists.txt
+++ b/llvm/examples/OrcV2Examples/OrcV2CBindingsIRTransforms/CMakeLists.txt
@@ -13,4 +13,6 @@ set(LLVM_LINK_COMPONENTS
 
 add_llvm_example(OrcV2CBindingsIRTransforms
   OrcV2CBindingsIRTransforms.c
+
+  DISABLE_PCH_REUSE # no C++ files, prevent CMake error.
   )
diff --git a/llvm/examples/OrcV2Examples/OrcV2CBindingsLazy/CMakeLists.txt b/llvm/examples/OrcV2Examples/OrcV2CBindingsLazy/CMakeLists.txt
index 52eb2d496fc23..c46700d9b2576 100644
--- a/llvm/examples/OrcV2Examples/OrcV2CBindingsLazy/CMakeLists.txt
+++ b/llvm/examples/OrcV2Examples/OrcV2CBindingsLazy/CMakeLists.txt
@@ -12,4 +12,6 @@ set(LLVM_LINK_COMPONENTS
 
 add_llvm_example(OrcV2CBindingsLazy
   OrcV2CBindingsLazy.c
+
+  DISABLE_PCH_REUSE # no C++ files, prevent CMake error.
   )
diff --git a/llvm/examples/OrcV2Examples/OrcV2CBindingsMCJITLikeMemoryManager/CMakeLists.txt b/llvm/examples/OrcV2Examples/OrcV2CBindingsMCJITLikeMemoryManager/CMakeLists.txt
index 4cdc2b114e9a5..d4a06104120c8 100644
--- a/llvm/examples/OrcV2Examples/OrcV2CBindingsMCJITLikeMemoryManager/CMakeLists.txt
+++ b/llvm/examples/OrcV2Examples/OrcV2CBindingsMCJITLikeMemoryManager/CMakeLists.txt
@@ -12,4 +12,6 @@ set(LLVM_LINK_COMPONENTS
 
 add_llvm_exampl...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Jan 16, 2026

@llvm/pr-subscribers-testing-tools

Author: Alexis Engelke (aengelke)

Changes

This patch implements PCH support.

  • Libraries can defined precompiled headers using a newly added PRECOMPILE_HEADERS keyword. If specified, the listed headers will be compiled into a pre-compiled header using standard CMake mechanisms.

  • Libraries that don't define their own PRECOMPILE_HEADERS but directly depend on a library or component that defines its own PCH will reuse that PCH. This reuse is not transitive to prevent excessive use of unrelated headers. If multiple dependencies provide a reusable PCH, the first one with the longest dependency chain (stored in the CMake target property LLVM_PCH_PRIORITY) is used. However, due to CMake limitations, only PCH from targets that are already defined can be reused; therefore libraries that should reuse a PCH must be defined later in the CMake file (=> add_subdirectory order matters).

  • Libraries and executables can prevent PCH reuse with the keyword DISABLE_PCH_REUSE. This both prevents reuse from dependencies and reuse by other dependants. This is useful when, e.g., internal headers are used in the PCH or the used headers are unlikely to provide benefits for dependants.

  • Precompiled headers are only used for C++ targets. Due to CMake-weirdness, C-only targets must explicitly disable PCH reuse, otherwise configuration fails.

  • With GCC, PCH provide very little benefits (tested with GCC 14 and 15) due to increased template instatiation costs, but substantially increase max-rss and build directory size. Therefore, disable PCH with GCC by default; this can be explicitly overridden on the command line with -DCMAKE_DISABLE_PRECOMPILE_HEADERS=OFF.

  • With ccache and non-Clang compilers, changes in macro definitions are not always accurately forwarded with ccache's preprocessed mode. To be on the safe side, when ccache is enabled, disable PCH with all non-Clang compilers; this can be explicitly overridden.

  • Add a base PCH to LLVMSupport, which includes widely used standard library and Support+ADT headers. The pch.h is placed in include so that later PCH headers can extend that list of headers.

  • Flang PCH use is ported to the general mechanism.

Addition of PCH headers for other components (e.g., IR, CodeGen) will be posted as separate PRs.

RFC: https://discourse.llvm.org/t/rfc-use-pre-compiled-headers-to-speed-up-llvm-build-by-1-5-2x/89345


Patch is 25.98 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/176420.diff

25 Files Affected:

  • (modified) clang/tools/c-index-test/CMakeLists.txt (+2)
  • (modified) clang/tools/clang-fuzzer/dictionary/CMakeLists.txt (+2)
  • (modified) flang/CMakeLists.txt (-10)
  • (modified) flang/lib/Evaluate/CMakeLists.txt (+9-9)
  • (modified) flang/lib/Frontend/CMakeLists.txt (+8-8)
  • (modified) flang/lib/Lower/CMakeLists.txt (+11-11)
  • (modified) flang/lib/Parser/CMakeLists.txt (+8-8)
  • (modified) flang/lib/Semantics/CMakeLists.txt (+9-9)
  • (modified) llvm/CMakeLists.txt (+16)
  • (modified) llvm/cmake/modules/AddLLVM.cmake (+69-8)
  • (modified) llvm/cmake/modules/HandleLLVMOptions.cmake (+21)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsAddObjectFile/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsBasicUsage/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsDumpObjects/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsIRTransforms/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsLazy/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsMCJITLikeMemoryManager/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsRemovableCode/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsVeryLazy/CMakeLists.txt (+2)
  • (added) llvm/include/llvm/Support/pch.h (+75)
  • (modified) llvm/lib/CMakeLists.txt (+1-1)
  • (modified) llvm/lib/Support/CMakeLists.txt (+6-1)
  • (modified) llvm/tools/llvm-c-test/CMakeLists.txt (+2)
  • (modified) llvm/utils/count/CMakeLists.txt (+2)
  • (modified) mlir/test/CAPI/CMakeLists.txt (+1)
diff --git a/clang/tools/c-index-test/CMakeLists.txt b/clang/tools/c-index-test/CMakeLists.txt
index 41e80e66ffa7a..2bbd387d6e812 100644
--- a/clang/tools/c-index-test/CMakeLists.txt
+++ b/clang/tools/c-index-test/CMakeLists.txt
@@ -5,6 +5,8 @@ set(LLVM_LINK_COMPONENTS
 add_clang_executable(c-index-test
   c-index-test.c
   core_main.cpp
+
+  DISABLE_PCH_REUSE # Prevent CMake error with C source files.
   )
 
 if(NOT MSVC)
diff --git a/clang/tools/clang-fuzzer/dictionary/CMakeLists.txt b/clang/tools/clang-fuzzer/dictionary/CMakeLists.txt
index 6b72b98f5e1c4..a7f18965b4e29 100644
--- a/clang/tools/clang-fuzzer/dictionary/CMakeLists.txt
+++ b/clang/tools/clang-fuzzer/dictionary/CMakeLists.txt
@@ -1,5 +1,7 @@
 set(CMAKE_CXX_FLAGS ${CXX_FLAGS_NOFUZZ})
 add_clang_executable(clang-fuzzer-dictionary
   dictionary.c
+
+  DISABLE_PCH_REUSE # no C++ files, prevent CMake error.
   )
 
diff --git a/flang/CMakeLists.txt b/flang/CMakeLists.txt
index c01eb56d5e496..e21304d2e4da7 100644
--- a/flang/CMakeLists.txt
+++ b/flang/CMakeLists.txt
@@ -441,11 +441,6 @@ if (LLVM_COMPILER_IS_GCC_COMPATIBLE)
   if (BUILD_SHARED_LIBS AND NOT (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-semantic-interposition")
   endif()
-
-  # GCC requires this flag in order for precompiled headers to work with ccache
-  if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT CMAKE_DISABLE_PRECOMPILE_HEADERS)
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fpch-preprocess")
-  endif()
 endif()
 
 # Clang on Darwin enables non-POSIX extensions by default, which allows the
@@ -456,11 +451,6 @@ if (APPLE)
   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_POSIX_C_SOURCE=200809")
 endif()
 
-# Clang requires this flag in order for precompiled headers to work with ccache
-if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT CMAKE_DISABLE_PRECOMPILE_HEADERS)
-  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xclang -fno-pch-timestamp")
-endif()
-
 list(REMOVE_DUPLICATES CMAKE_CXX_FLAGS)
 
 # Determine HOST_LINK_VERSION on Darwin.
diff --git a/flang/lib/Evaluate/CMakeLists.txt b/flang/lib/Evaluate/CMakeLists.txt
index 24a1c9004bc3b..472ecb6d8d079 100644
--- a/flang/lib/Evaluate/CMakeLists.txt
+++ b/flang/lib/Evaluate/CMakeLists.txt
@@ -66,15 +66,8 @@ add_flang_library(FortranEvaluate
   ${LIBPGMATH}
   ${QUADMATHLIB}
 
-  LINK_COMPONENTS
-  Support
-
-  DEPENDS
-  acc_gen
-  omp_gen
-)
-
-target_precompile_headers(FortranEvaluate PRIVATE
+  DISABLE_PCH_REUSE
+  PRECOMPILE_HEADERS
   [["flang/Evaluate/common.h"]]
   [["flang/Evaluate/call.h"]]
   [["flang/Evaluate/traverse.h"]]
@@ -86,4 +79,11 @@ target_precompile_headers(FortranEvaluate PRIVATE
   [["flang/Evaluate/integer.h"]]
   [["flang/Evaluate/expression.h"]]
   [["flang/Evaluate/tools.h"]]
+
+  LINK_COMPONENTS
+  Support
+
+  DEPENDS
+  acc_gen
+  omp_gen
 )
diff --git a/flang/lib/Frontend/CMakeLists.txt b/flang/lib/Frontend/CMakeLists.txt
index 4ebe497e65676..bd67ec444f358 100644
--- a/flang/lib/Frontend/CMakeLists.txt
+++ b/flang/lib/Frontend/CMakeLists.txt
@@ -13,6 +13,14 @@ add_flang_library(flangFrontend
   TextDiagnosticBuffer.cpp
   TextDiagnostic.cpp
 
+  DISABLE_PCH_REUSE
+  PRECOMPILE_HEADERS
+  [["flang/Parser/parsing.h"]]
+  [["flang/Parser/parse-tree.h"]]
+  [["flang/Parser/dump-parse-tree.h"]]
+  [["flang/Lower/PFTBuilder.h"]]
+  [["flang/Lower/Bridge.h"]]
+
   DEPENDS
   CUFDialect
   FIRDialect
@@ -78,11 +86,3 @@ add_flang_library(flangFrontend
   clangBasic
   clangOptions
 )
-
-target_precompile_headers(flangFrontend PRIVATE
-  [["flang/Parser/parsing.h"]]
-  [["flang/Parser/parse-tree.h"]]
-  [["flang/Parser/dump-parse-tree.h"]]
-  [["flang/Lower/PFTBuilder.h"]]
-  [["flang/Lower/Bridge.h"]]
-)
diff --git a/flang/lib/Lower/CMakeLists.txt b/flang/lib/Lower/CMakeLists.txt
index 230a56ab66ec5..f5424c78b9a98 100644
--- a/flang/lib/Lower/CMakeLists.txt
+++ b/flang/lib/Lower/CMakeLists.txt
@@ -38,6 +38,17 @@ add_flang_library(FortranLower
   Support/Utils.cpp
   SymbolMap.cpp
   VectorSubscripts.cpp
+
+  DISABLE_PCH_REUSE
+  PRECOMPILE_HEADERS
+  [["flang/Lower/ConvertExpr.h"]]
+  [["flang/Lower/SymbolMap.h"]]
+  [["flang/Lower/AbstractConverter.h"]]
+  [["flang/Lower/IterationSpace.h"]]
+  [["flang/Lower/CallInterface.h"]]
+  [["flang/Lower/BoxAnalyzer.h"]]
+  [["flang/Lower/PFTBuilder.h"]]
+  [["flang/Lower/DirectivesCommon.h"]]
   
   DEPENDS
   CUFAttrs
@@ -79,14 +90,3 @@ add_flang_library(FortranLower
   MLIRLLVMDialect
   MLIRSCFToControlFlow
 )
-
-target_precompile_headers(FortranLower PRIVATE
-  [["flang/Lower/ConvertExpr.h"]]
-  [["flang/Lower/SymbolMap.h"]]
-  [["flang/Lower/AbstractConverter.h"]]
-  [["flang/Lower/IterationSpace.h"]]
-  [["flang/Lower/CallInterface.h"]]
-  [["flang/Lower/BoxAnalyzer.h"]]
-  [["flang/Lower/PFTBuilder.h"]]
-  [["flang/Lower/DirectivesCommon.h"]]
-)
diff --git a/flang/lib/Parser/CMakeLists.txt b/flang/lib/Parser/CMakeLists.txt
index 20c6c2a7c8f80..e0479b0da3eb8 100644
--- a/flang/lib/Parser/CMakeLists.txt
+++ b/flang/lib/Parser/CMakeLists.txt
@@ -25,6 +25,14 @@ add_flang_library(FortranParser
   unparse.cpp
   user-state.cpp
 
+  DISABLE_PCH_REUSE
+  PRECOMPILE_HEADERS
+  [["flang/Parser/parsing.h"]]
+  [["flang/Parser/parse-tree.h"]]
+  [["flang/Parser/provenance.h"]]
+  [["flang/Parser/message.h"]]
+  [["flang/Parser/parse-tree-visitor.h"]]
+
   LINK_LIBS
   FortranSupport
 
@@ -37,11 +45,3 @@ add_flang_library(FortranParser
   omp_gen
   acc_gen
 )
-
-target_precompile_headers(FortranParser PRIVATE
-  [["flang/Parser/parsing.h"]]
-  [["flang/Parser/parse-tree.h"]]
-  [["flang/Parser/provenance.h"]]
-  [["flang/Parser/message.h"]]
-  [["flang/Parser/parse-tree-visitor.h"]]
-)
diff --git a/flang/lib/Semantics/CMakeLists.txt b/flang/lib/Semantics/CMakeLists.txt
index 109bc2dbb8569..44e6dfb4dd09f 100644
--- a/flang/lib/Semantics/CMakeLists.txt
+++ b/flang/lib/Semantics/CMakeLists.txt
@@ -53,6 +53,15 @@ add_flang_library(FortranSemantics
   type.cpp
   unparse-with-symbols.cpp
 
+  DISABLE_PCH_REUSE
+  PRECOMPILE_HEADERS
+  [["flang/Semantics/semantics.h"]]
+  [["flang/Semantics/type.h"]]
+  [["flang/Semantics/openmp-modifiers.h"]]
+  [["flang/Semantics/expression.h"]]
+  [["flang/Semantics/tools.h"]]
+  [["flang/Semantics/symbol.h"]]
+
   DEPENDS
   acc_gen
   omp_gen
@@ -68,12 +77,3 @@ add_flang_library(FortranSemantics
   FrontendOpenACC
   TargetParser
 )
-
-target_precompile_headers(FortranSemantics PRIVATE
-  [["flang/Semantics/semantics.h"]]
-  [["flang/Semantics/type.h"]]
-  [["flang/Semantics/openmp-modifiers.h"]]
-  [["flang/Semantics/expression.h"]]
-  [["flang/Semantics/tools.h"]]
-  [["flang/Semantics/symbol.h"]]
-)
diff --git a/llvm/CMakeLists.txt b/llvm/CMakeLists.txt
index 9954053591d30..a539a2182efa5 100644
--- a/llvm/CMakeLists.txt
+++ b/llvm/CMakeLists.txt
@@ -313,6 +313,22 @@ if(LLVM_CCACHE_BUILD)
         set(CCACHE_PROGRAM "CCACHE_DIR=${LLVM_CCACHE_DIR} ${CCACHE_PROGRAM}")
       endif()
       set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_PROGRAM})
+
+      if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+        # ccache with PCH can lead to false-positives when only a macro
+        # definition changes with non-Clang compilers, because macro definitions
+        # are not compared in preprocessed mode.
+        # See: https://github.com/ccache/ccache/issues/1668
+        if(NOT DEFINED CMAKE_DISABLE_PRECOMPILE_HEADERS)
+          message(NOTICE "Using ccache with precompiled headers with non-Clang "
+            "compilers is not supported. CMAKE_DISABLE_PRECOMPILE_HEADERS will be set to ON. "
+            "Pass -DCMAKE_DISABLE_PRECOMPILE_HEADERS=OFF to override this.")
+          set(CMAKE_DISABLE_PRECOMPILE_HEADERS "ON")
+        elseif(NOT CMAKE_DISABLE_PRECOMPILE_HEADERS)
+          message(WARNING "Using ccache with precompiled headers with non-Clang "
+            "compilers is not supported.")
+        endif()
+      endif()
     else()
       # Until a way to reliably configure ccache on Windows is found,
       # disable precompiled headers for Windows + ccache builds
diff --git a/llvm/cmake/modules/AddLLVM.cmake b/llvm/cmake/modules/AddLLVM.cmake
index d938214f9d0df..c9ed03d98cc9c 100644
--- a/llvm/cmake/modules/AddLLVM.cmake
+++ b/llvm/cmake/modules/AddLLVM.cmake
@@ -79,6 +79,48 @@ function(llvm_update_compile_flags name)
   target_compile_definitions(${name} PRIVATE ${LLVM_COMPILE_DEFINITIONS})
 endfunction()
 
+function(llvm_update_pch name)
+  if(LLVM_REQUIRES_RTTI OR LLVM_REQUIRES_EH)
+    # Non-default RTTI/EH results in incompatible flags, precluding PCH reuse.
+    set(ARG_DISABLE_PCH_REUSE ON)
+  endif()
+
+  # Find PCH with highest priority from dependencies. We reuse the first PCH
+  # with the highest priority. If the target has its own set of PCH, we give it
+  # a higher priority so that dependents will prefer the new PCH. We don't do
+  # transitive PCH reuse, because this causes too many unrelated naming
+  # collisions (e.g., in A -> B -> C{pch}, only B would reuse the PCH of C).
+  set(pch_priority 0)
+  llvm_map_components_to_libnames(libs
+    ${LLVM_LINK_COMPONENTS}
+  )
+  list(APPEND libs ${ARG_LINK_LIBS})
+  foreach(lib ${libs})
+    if(TARGET ${lib})
+      get_target_property(lib_pch_priority ${lib} LLVM_PCH_PRIORITY)
+      if(${lib_pch_priority} GREATER ${pch_priority})
+        set(pch_priority ${lib_pch_priority})
+        set(pch_reuse ${lib})
+      endif()
+    endif()
+  endforeach()
+
+  if(ARG_PRECOMPILE_HEADERS)
+    message(DEBUG "Adding PCH ${ARG_PRECOMPILE_HEADERS} for ${name} (prio ${pch_priority})")
+    target_precompile_headers(${name} PRIVATE $<$<COMPILE_LANGUAGE:CXX>:${ARG_PRECOMPILE_HEADERS}>)
+    if(NOT ARG_DISABLE_PCH_REUSE)
+      # Set priority so that dependants can reuse the PCH.
+      math(EXPR pch_priority "${pch_priority} + 1")
+      set_target_properties(${name} PROPERTIES LLVM_PCH_PRIORITY ${pch_priority})
+    endif()
+  elseif(pch_reuse AND NOT ARG_DISABLE_PCH_REUSE)
+    message(DEBUG "Using PCH ${pch_reuse} for ${name} (prio ${pch_priority})")
+    target_precompile_headers(${name} REUSE_FROM ${pch_reuse})
+  else()
+    message(DEBUG "Using NO PCH for ${name}")
+  endif()
+endfunction()
+
 function(add_llvm_symbol_exports target_name export_file)
   if("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin")
     set(native_export_file "${target_name}.exports")
@@ -485,6 +527,13 @@ endfunction(set_windows_version_resource_properties)
 #     Corresponds to OUTPUT_NAME in target properties.
 #   DEPENDS targets...
 #     Same semantics as add_dependencies().
+#   PRECOMPILE_HEADERS include_directives...
+#     Pre-compiled C++ headers to use. PCH can be reused by dependants. If
+#     specified, no PCHs from dependencies will be reused.
+#   DISABLE_PCH_REUSE
+#     Disable reuse of pre-compiled headers in both directions: the library will
+#     not reuse the PCH of a dependency and a defined PCH will not be offered
+#     for reuse by dependants.
 #   LINK_COMPONENTS components...
 #     Same as the variable LLVM_LINK_COMPONENTS.
 #   LINK_LIBS lib_targets...
@@ -504,11 +553,12 @@ endfunction(set_windows_version_resource_properties)
 #   )
 function(llvm_add_library name)
   cmake_parse_arguments(ARG
-    "MODULE;SHARED;STATIC;OBJECT;DISABLE_LLVM_LINK_LLVM_DYLIB;SONAME;NO_INSTALL_RPATH;COMPONENT_LIB"
+    "MODULE;SHARED;STATIC;OBJECT;DISABLE_LLVM_LINK_LLVM_DYLIB;SONAME;NO_INSTALL_RPATH;COMPONENT_LIB;DISABLE_PCH_REUSE"
     "OUTPUT_NAME;PLUGIN_TOOL;ENTITLEMENTS;BUNDLE_PATH"
-    "ADDITIONAL_HEADERS;DEPENDS;LINK_COMPONENTS;LINK_LIBS;OBJLIBS"
+    "ADDITIONAL_HEADERS;PRECOMPILE_HEADERS;DEPENDS;LINK_COMPONENTS;LINK_LIBS;OBJLIBS"
     ${ARGN})
   list(APPEND LLVM_COMMON_DEPENDS ${ARG_DEPENDS})
+  list(APPEND LLVM_LINK_COMPONENTS ${ARG_LINK_COMPONENTS})
   if(ARG_ADDITIONAL_HEADERS)
     # Pass through ADDITIONAL_HEADERS.
     set(ARG_ADDITIONAL_HEADERS ADDITIONAL_HEADERS ${ARG_ADDITIONAL_HEADERS})
@@ -550,6 +600,7 @@ function(llvm_add_library name)
       ${ALL_FILES}
       )
     llvm_update_compile_flags(${obj_name})
+    llvm_update_pch(${obj_name})
     if(CMAKE_GENERATOR STREQUAL "Xcode")
       set(DUMMY_FILE ${CMAKE_CURRENT_BINARY_DIR}/Dummy.c)
       file(WRITE ${DUMMY_FILE} "// This file intentionally empty\n")
@@ -604,7 +655,7 @@ function(llvm_add_library name)
       ${output_name}
       OBJLIBS ${ALL_FILES} # objlib
       LINK_LIBS ${ARG_LINK_LIBS}
-      LINK_COMPONENTS ${ARG_LINK_COMPONENTS}
+      LINK_COMPONENTS ${LLVM_LINK_COMPONENTS}
       )
     set_target_properties(${name_static} PROPERTIES FOLDER "${subproject_title}/Libraries")
 
@@ -676,6 +727,11 @@ function(llvm_add_library name)
   # $<TARGET_OBJECTS> doesn't require compile flags.
   if(NOT obj_name)
     llvm_update_compile_flags(${name})
+    llvm_update_pch(${name})
+  else()
+    target_precompile_headers(${name} REUSE_FROM ${obj_name})
+    get_target_property(pch_priority ${obj_name} LLVM_PCH_PRIORITY)
+    set_target_properties(${name} PROPERTIES LLVM_PCH_PRIORITY ${pch_priority})
   endif()
   add_link_opts( ${name} )
   if(ARG_OUTPUT_NAME)
@@ -758,8 +814,7 @@ function(llvm_add_library name)
         target_compile_definitions(${name} PRIVATE LLVM_BUILD_STATIC)
       endif()
       llvm_map_components_to_libnames(llvm_libs
-       ${ARG_LINK_COMPONENTS}
-       ${LLVM_LINK_COMPONENTS}
+        ${LLVM_LINK_COMPONENTS}
        )
     endif()
   else()
@@ -770,7 +825,7 @@ function(llvm_add_library name)
     # It would be nice to verify that we have the dependencies for this library
     # name, but using get_property(... SET) doesn't suffice to determine if a
     # property has been set to an empty value.
-    set_property(TARGET ${name} PROPERTY LLVM_LINK_COMPONENTS ${ARG_LINK_COMPONENTS} ${LLVM_LINK_COMPONENTS})
+    set_property(TARGET ${name} PROPERTY LLVM_LINK_COMPONENTS ${LLVM_LINK_COMPONENTS})
 
     # This property is an internal property only used to make sure the
     # link step applied in LLVMBuildResolveComponentsLink uses the same
@@ -993,6 +1048,7 @@ macro(generate_llvm_objects name)
       ${ALL_FILES}
       )
     llvm_update_compile_flags(${obj_name})
+    llvm_update_pch(${obj_name})
     set(ALL_FILES "$<TARGET_OBJECTS:${obj_name}>")
     if(ARG_DEPENDS)
       add_dependencies(${obj_name} ${ARG_DEPENDS})
@@ -1032,7 +1088,7 @@ endmacro()
 
 macro(add_llvm_executable name)
   cmake_parse_arguments(ARG
-    "DISABLE_LLVM_LINK_LLVM_DYLIB;IGNORE_EXTERNALIZE_DEBUGINFO;NO_INSTALL_RPATH;SUPPORT_PLUGINS;EXPORT_SYMBOLS"
+    "DISABLE_LLVM_LINK_LLVM_DYLIB;IGNORE_EXTERNALIZE_DEBUGINFO;NO_INSTALL_RPATH;SUPPORT_PLUGINS;EXPORT_SYMBOLS;DISABLE_PCH_REUSE"
     "ENTITLEMENTS;BUNDLE_PATH"
     ""
     ${ARGN})
@@ -1073,6 +1129,11 @@ macro(add_llvm_executable name)
   # $<TARGET_OBJECTS> doesn't require compile flags.
   if(NOT LLVM_ENABLE_OBJLIB)
     llvm_update_compile_flags(${name})
+    llvm_update_pch(${name})
+  else()
+    target_precompile_headers(${name} REUSE_FROM ${obj_name})
+    get_target_property(pch_priority ${obj_name} LLVM_PCH_PRIORITY)
+    set_target_properties(${name} PROPERTIES LLVM_PCH_PRIORITY ${pch_priority})
   endif()
 
   if (ARG_SUPPORT_PLUGINS AND NOT "${CMAKE_SYSTEM_NAME}" MATCHES "AIX")
@@ -1780,7 +1841,7 @@ function(add_unittest test_suite test_name)
   endif()
 
   list(APPEND LLVM_LINK_COMPONENTS Support) # gtest needs it for raw_ostream
-  add_llvm_executable(${test_name} IGNORE_EXTERNALIZE_DEBUGINFO NO_INSTALL_RPATH ${ARGN})
+  add_llvm_executable(${test_name} IGNORE_EXTERNALIZE_DEBUGINFO NO_INSTALL_RPATH DISABLE_PCH_REUSE ${ARGN})
   get_subproject_title(subproject_title)
   set_target_properties(${test_name} PROPERTIES FOLDER "${subproject_title}/Tests/Unit")
 
diff --git a/llvm/cmake/modules/HandleLLVMOptions.cmake b/llvm/cmake/modules/HandleLLVMOptions.cmake
index 311123084bf58..2e90c0ad0ad76 100644
--- a/llvm/cmake/modules/HandleLLVMOptions.cmake
+++ b/llvm/cmake/modules/HandleLLVMOptions.cmake
@@ -1304,6 +1304,27 @@ if (LLVM_BUILD_INSTRUMENTED AND LLVM_BUILD_INSTRUMENTED_COVERAGE)
   message(FATAL_ERROR "LLVM_BUILD_INSTRUMENTED and LLVM_BUILD_INSTRUMENTED_COVERAGE cannot both be specified")
 endif()
 
+if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT DEFINED CMAKE_DISABLE_PRECOMPILE_HEADERS)
+  # Pre-compiled headers with GCC (tested versions 14+15) provide very little
+  # compile-time improvements, but substantially increase the build dir size.
+  # Therefore, disable PCH with GCC by default.
+  message(NOTICE "Precompiled headers are disabled by default with GCC. "
+    "Pass -DCMAKE_DISABLE_PRECOMPILE_HEADERS=OFF to override.")
+  set(CMAKE_DISABLE_PRECOMPILE_HEADERS ON)
+endif()
+if(NOT CMAKE_DISABLE_PRECOMPILE_HEADERS)
+  message(STATUS "Precompiled headers enabled.")
+  if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+    # Clang requires this flag in order for precompiled headers to work with ccache
+    append("-Xclang -fno-pch-timestamp" CMAKE_CXX_FLAGS)
+  elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+    # GCC requires this flag in order for precompiled headers to work with ccache
+    append("-fpch-preprocess" CMAKE_CXX_FLAGS)
+  endif()
+else()
+  message(STATUS "Precompiled headers disabled.")
+endif()
+
 set(LLVM_THINLTO_CACHE_PATH "${PROJECT_BINARY_DIR}/lto.cache" CACHE STRING "Set ThinLTO cache path. This can be used when building LLVM from several different directiories.")
 
 if(LLVM_ENABLE_LTO AND WIN32 AND NOT LINKER_IS_LLD_LINK AND NOT MINGW)
diff --git a/llvm/examples/OrcV2Examples/OrcV2CBindingsAddObjectFile/CMakeLists.txt b/llvm/examples/OrcV2Examples/OrcV2CBindingsAddObjectFile/CMakeLists.txt
index cc50112f326ea..9a82cc0ed0916 100644
--- a/llvm/examples/OrcV2Examples/OrcV2CBindingsAddObjectFile/CMakeLists.txt
+++ b/llvm/examples/OrcV2Examples/OrcV2CBindingsAddObjectFile/CMakeLists.txt
@@ -12,4 +12,6 @@ set(LLVM_LINK_COMPONENTS
 
 add_llvm_example(OrcV2CBindingsAddObjectFile
   OrcV2CBindingsAddObjectFile.c
+
+  DISABLE_PCH_REUSE # no C++ files, prevent CMake error.
   )
diff --git a/llvm/examples/OrcV2Examples/OrcV2CBindingsBasicUsage/CMakeLists.txt b/llvm/examples/OrcV2Examples/OrcV2CBindingsBasicUsage/CMakeLists.txt
index 0f18d6c24912f..1a738f60d15e1 100644
--- a/llvm/examples/OrcV2Examples/OrcV2CBindingsBasicUsage/CMakeLists.txt
+++ b/llvm/examples/OrcV2Examples/OrcV2CBindingsBasicUsage/CMakeLists.txt
@@ -12,4 +12,6 @@ set(LLVM_LINK_COMPONENTS
 
 add_llvm_example(OrcV2CBindingsBasicUsage
   OrcV2CBindingsBasicUsage.c
+
+  DISABLE_PCH_REUSE # no C++ files, prevent CMake error.
   )
diff --git a/llvm/examples/OrcV2Examples/OrcV2CBindingsDumpObjects/CMakeLists.txt b/llvm/examples/OrcV2Examples/OrcV2CBindingsDumpObjects/CMakeLists.txt
index 8e2c97d782062..c7f748a12bf35 100644
--- a/llvm/examples/OrcV2Examples/OrcV2CBindingsDumpObjects/CMakeLists.txt
+++ b/llvm/examples/OrcV2Examples/OrcV2CBindingsDumpObjects/CMakeLists.txt
@@ -12,4 +12,6 @@ set(LLVM_LINK_COMPONENTS
 
 add_llvm_example(OrcV2CBindingsDumpObjects
   OrcV2CBindingsDumpObjects.c
+
+  DISABLE_PCH_REUSE # no C++ files, prevent CMake error.
   )
diff --git a/llvm/examples/OrcV2Examples/OrcV2CBindingsIRTransforms/CMakeLists.txt b/llvm/examples/OrcV2Examples/OrcV2CBindingsIRTransforms/CMakeLists.txt
index af1b43e43e111..b152faf1e3527 100644
--- a/llvm/examples/OrcV2Examples/OrcV2CBindingsIRTransforms/CMakeLists.txt
+++ b/llvm/examples/OrcV2Examples/OrcV2CBindingsIRTransforms/CMakeLists.txt
@@ -13,4 +13,6 @@ set(LLVM_LINK_COMPONENTS
 
 add_llvm_example(OrcV2CBindingsIRTransforms
   OrcV2CBindingsIRTransforms.c
+
+  DISABLE_PCH_REUSE # no C++ files, prevent CMake error.
   )
diff --git a/llvm/examples/OrcV2Examples/OrcV2CBindingsLazy/CMakeLists.txt b/llvm/examples/OrcV2Examples/OrcV2CBindingsLazy/CMakeLists.txt
index 52eb2d496fc23..c46700d9b2576 100644
--- a/llvm/examples/OrcV2Examples/OrcV2CBindingsLazy/CMakeLists.txt
+++ b/llvm/examples/OrcV2Examples/OrcV2CBindingsLazy/CMakeLists.txt
@@ -12,4 +12,6 @@ set(LLVM_LINK_COMPONENTS
 
 add_llvm_example(OrcV2CBindingsLazy
   OrcV2CBindingsLazy.c
+
+  DISABLE_PCH_REUSE # no C++ files, prevent CMake error.
   )
diff --git a/llvm/examples/OrcV2Examples/OrcV2CBindingsMCJITLikeMemoryManager/CMakeLists.txt b/llvm/examples/OrcV2Examples/OrcV2CBindingsMCJITLikeMemoryManager/CMakeLists.txt
index 4cdc2b114e9a5..d4a06104120c8 100644
--- a/llvm/examples/OrcV2Examples/OrcV2CBindingsMCJITLikeMemoryManager/CMakeLists.txt
+++ b/llvm/examples/OrcV2Examples/OrcV2CBindingsMCJITLikeMemoryManager/CMakeLists.txt
@@ -12,4 +12,6 @@ set(LLVM_LINK_COMPONENTS
 
 add_llvm_exampl...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Jan 16, 2026

@llvm/pr-subscribers-flang-fir-hlfir

Author: Alexis Engelke (aengelke)

Changes

This patch implements PCH support.

  • Libraries can defined precompiled headers using a newly added PRECOMPILE_HEADERS keyword. If specified, the listed headers will be compiled into a pre-compiled header using standard CMake mechanisms.

  • Libraries that don't define their own PRECOMPILE_HEADERS but directly depend on a library or component that defines its own PCH will reuse that PCH. This reuse is not transitive to prevent excessive use of unrelated headers. If multiple dependencies provide a reusable PCH, the first one with the longest dependency chain (stored in the CMake target property LLVM_PCH_PRIORITY) is used. However, due to CMake limitations, only PCH from targets that are already defined can be reused; therefore libraries that should reuse a PCH must be defined later in the CMake file (=> add_subdirectory order matters).

  • Libraries and executables can prevent PCH reuse with the keyword DISABLE_PCH_REUSE. This both prevents reuse from dependencies and reuse by other dependants. This is useful when, e.g., internal headers are used in the PCH or the used headers are unlikely to provide benefits for dependants.

  • Precompiled headers are only used for C++ targets. Due to CMake-weirdness, C-only targets must explicitly disable PCH reuse, otherwise configuration fails.

  • With GCC, PCH provide very little benefits (tested with GCC 14 and 15) due to increased template instatiation costs, but substantially increase max-rss and build directory size. Therefore, disable PCH with GCC by default; this can be explicitly overridden on the command line with -DCMAKE_DISABLE_PRECOMPILE_HEADERS=OFF.

  • With ccache and non-Clang compilers, changes in macro definitions are not always accurately forwarded with ccache's preprocessed mode. To be on the safe side, when ccache is enabled, disable PCH with all non-Clang compilers; this can be explicitly overridden.

  • Add a base PCH to LLVMSupport, which includes widely used standard library and Support+ADT headers. The pch.h is placed in include so that later PCH headers can extend that list of headers.

  • Flang PCH use is ported to the general mechanism.

Addition of PCH headers for other components (e.g., IR, CodeGen) will be posted as separate PRs.

RFC: https://discourse.llvm.org/t/rfc-use-pre-compiled-headers-to-speed-up-llvm-build-by-1-5-2x/89345


Patch is 25.98 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/176420.diff

25 Files Affected:

  • (modified) clang/tools/c-index-test/CMakeLists.txt (+2)
  • (modified) clang/tools/clang-fuzzer/dictionary/CMakeLists.txt (+2)
  • (modified) flang/CMakeLists.txt (-10)
  • (modified) flang/lib/Evaluate/CMakeLists.txt (+9-9)
  • (modified) flang/lib/Frontend/CMakeLists.txt (+8-8)
  • (modified) flang/lib/Lower/CMakeLists.txt (+11-11)
  • (modified) flang/lib/Parser/CMakeLists.txt (+8-8)
  • (modified) flang/lib/Semantics/CMakeLists.txt (+9-9)
  • (modified) llvm/CMakeLists.txt (+16)
  • (modified) llvm/cmake/modules/AddLLVM.cmake (+69-8)
  • (modified) llvm/cmake/modules/HandleLLVMOptions.cmake (+21)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsAddObjectFile/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsBasicUsage/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsDumpObjects/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsIRTransforms/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsLazy/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsMCJITLikeMemoryManager/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsRemovableCode/CMakeLists.txt (+2)
  • (modified) llvm/examples/OrcV2Examples/OrcV2CBindingsVeryLazy/CMakeLists.txt (+2)
  • (added) llvm/include/llvm/Support/pch.h (+75)
  • (modified) llvm/lib/CMakeLists.txt (+1-1)
  • (modified) llvm/lib/Support/CMakeLists.txt (+6-1)
  • (modified) llvm/tools/llvm-c-test/CMakeLists.txt (+2)
  • (modified) llvm/utils/count/CMakeLists.txt (+2)
  • (modified) mlir/test/CAPI/CMakeLists.txt (+1)
diff --git a/clang/tools/c-index-test/CMakeLists.txt b/clang/tools/c-index-test/CMakeLists.txt
index 41e80e66ffa7a..2bbd387d6e812 100644
--- a/clang/tools/c-index-test/CMakeLists.txt
+++ b/clang/tools/c-index-test/CMakeLists.txt
@@ -5,6 +5,8 @@ set(LLVM_LINK_COMPONENTS
 add_clang_executable(c-index-test
   c-index-test.c
   core_main.cpp
+
+  DISABLE_PCH_REUSE # Prevent CMake error with C source files.
   )
 
 if(NOT MSVC)
diff --git a/clang/tools/clang-fuzzer/dictionary/CMakeLists.txt b/clang/tools/clang-fuzzer/dictionary/CMakeLists.txt
index 6b72b98f5e1c4..a7f18965b4e29 100644
--- a/clang/tools/clang-fuzzer/dictionary/CMakeLists.txt
+++ b/clang/tools/clang-fuzzer/dictionary/CMakeLists.txt
@@ -1,5 +1,7 @@
 set(CMAKE_CXX_FLAGS ${CXX_FLAGS_NOFUZZ})
 add_clang_executable(clang-fuzzer-dictionary
   dictionary.c
+
+  DISABLE_PCH_REUSE # no C++ files, prevent CMake error.
   )
 
diff --git a/flang/CMakeLists.txt b/flang/CMakeLists.txt
index c01eb56d5e496..e21304d2e4da7 100644
--- a/flang/CMakeLists.txt
+++ b/flang/CMakeLists.txt
@@ -441,11 +441,6 @@ if (LLVM_COMPILER_IS_GCC_COMPATIBLE)
   if (BUILD_SHARED_LIBS AND NOT (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-semantic-interposition")
   endif()
-
-  # GCC requires this flag in order for precompiled headers to work with ccache
-  if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT CMAKE_DISABLE_PRECOMPILE_HEADERS)
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fpch-preprocess")
-  endif()
 endif()
 
 # Clang on Darwin enables non-POSIX extensions by default, which allows the
@@ -456,11 +451,6 @@ if (APPLE)
   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_POSIX_C_SOURCE=200809")
 endif()
 
-# Clang requires this flag in order for precompiled headers to work with ccache
-if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT CMAKE_DISABLE_PRECOMPILE_HEADERS)
-  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xclang -fno-pch-timestamp")
-endif()
-
 list(REMOVE_DUPLICATES CMAKE_CXX_FLAGS)
 
 # Determine HOST_LINK_VERSION on Darwin.
diff --git a/flang/lib/Evaluate/CMakeLists.txt b/flang/lib/Evaluate/CMakeLists.txt
index 24a1c9004bc3b..472ecb6d8d079 100644
--- a/flang/lib/Evaluate/CMakeLists.txt
+++ b/flang/lib/Evaluate/CMakeLists.txt
@@ -66,15 +66,8 @@ add_flang_library(FortranEvaluate
   ${LIBPGMATH}
   ${QUADMATHLIB}
 
-  LINK_COMPONENTS
-  Support
-
-  DEPENDS
-  acc_gen
-  omp_gen
-)
-
-target_precompile_headers(FortranEvaluate PRIVATE
+  DISABLE_PCH_REUSE
+  PRECOMPILE_HEADERS
   [["flang/Evaluate/common.h"]]
   [["flang/Evaluate/call.h"]]
   [["flang/Evaluate/traverse.h"]]
@@ -86,4 +79,11 @@ target_precompile_headers(FortranEvaluate PRIVATE
   [["flang/Evaluate/integer.h"]]
   [["flang/Evaluate/expression.h"]]
   [["flang/Evaluate/tools.h"]]
+
+  LINK_COMPONENTS
+  Support
+
+  DEPENDS
+  acc_gen
+  omp_gen
 )
diff --git a/flang/lib/Frontend/CMakeLists.txt b/flang/lib/Frontend/CMakeLists.txt
index 4ebe497e65676..bd67ec444f358 100644
--- a/flang/lib/Frontend/CMakeLists.txt
+++ b/flang/lib/Frontend/CMakeLists.txt
@@ -13,6 +13,14 @@ add_flang_library(flangFrontend
   TextDiagnosticBuffer.cpp
   TextDiagnostic.cpp
 
+  DISABLE_PCH_REUSE
+  PRECOMPILE_HEADERS
+  [["flang/Parser/parsing.h"]]
+  [["flang/Parser/parse-tree.h"]]
+  [["flang/Parser/dump-parse-tree.h"]]
+  [["flang/Lower/PFTBuilder.h"]]
+  [["flang/Lower/Bridge.h"]]
+
   DEPENDS
   CUFDialect
   FIRDialect
@@ -78,11 +86,3 @@ add_flang_library(flangFrontend
   clangBasic
   clangOptions
 )
-
-target_precompile_headers(flangFrontend PRIVATE
-  [["flang/Parser/parsing.h"]]
-  [["flang/Parser/parse-tree.h"]]
-  [["flang/Parser/dump-parse-tree.h"]]
-  [["flang/Lower/PFTBuilder.h"]]
-  [["flang/Lower/Bridge.h"]]
-)
diff --git a/flang/lib/Lower/CMakeLists.txt b/flang/lib/Lower/CMakeLists.txt
index 230a56ab66ec5..f5424c78b9a98 100644
--- a/flang/lib/Lower/CMakeLists.txt
+++ b/flang/lib/Lower/CMakeLists.txt
@@ -38,6 +38,17 @@ add_flang_library(FortranLower
   Support/Utils.cpp
   SymbolMap.cpp
   VectorSubscripts.cpp
+
+  DISABLE_PCH_REUSE
+  PRECOMPILE_HEADERS
+  [["flang/Lower/ConvertExpr.h"]]
+  [["flang/Lower/SymbolMap.h"]]
+  [["flang/Lower/AbstractConverter.h"]]
+  [["flang/Lower/IterationSpace.h"]]
+  [["flang/Lower/CallInterface.h"]]
+  [["flang/Lower/BoxAnalyzer.h"]]
+  [["flang/Lower/PFTBuilder.h"]]
+  [["flang/Lower/DirectivesCommon.h"]]
   
   DEPENDS
   CUFAttrs
@@ -79,14 +90,3 @@ add_flang_library(FortranLower
   MLIRLLVMDialect
   MLIRSCFToControlFlow
 )
-
-target_precompile_headers(FortranLower PRIVATE
-  [["flang/Lower/ConvertExpr.h"]]
-  [["flang/Lower/SymbolMap.h"]]
-  [["flang/Lower/AbstractConverter.h"]]
-  [["flang/Lower/IterationSpace.h"]]
-  [["flang/Lower/CallInterface.h"]]
-  [["flang/Lower/BoxAnalyzer.h"]]
-  [["flang/Lower/PFTBuilder.h"]]
-  [["flang/Lower/DirectivesCommon.h"]]
-)
diff --git a/flang/lib/Parser/CMakeLists.txt b/flang/lib/Parser/CMakeLists.txt
index 20c6c2a7c8f80..e0479b0da3eb8 100644
--- a/flang/lib/Parser/CMakeLists.txt
+++ b/flang/lib/Parser/CMakeLists.txt
@@ -25,6 +25,14 @@ add_flang_library(FortranParser
   unparse.cpp
   user-state.cpp
 
+  DISABLE_PCH_REUSE
+  PRECOMPILE_HEADERS
+  [["flang/Parser/parsing.h"]]
+  [["flang/Parser/parse-tree.h"]]
+  [["flang/Parser/provenance.h"]]
+  [["flang/Parser/message.h"]]
+  [["flang/Parser/parse-tree-visitor.h"]]
+
   LINK_LIBS
   FortranSupport
 
@@ -37,11 +45,3 @@ add_flang_library(FortranParser
   omp_gen
   acc_gen
 )
-
-target_precompile_headers(FortranParser PRIVATE
-  [["flang/Parser/parsing.h"]]
-  [["flang/Parser/parse-tree.h"]]
-  [["flang/Parser/provenance.h"]]
-  [["flang/Parser/message.h"]]
-  [["flang/Parser/parse-tree-visitor.h"]]
-)
diff --git a/flang/lib/Semantics/CMakeLists.txt b/flang/lib/Semantics/CMakeLists.txt
index 109bc2dbb8569..44e6dfb4dd09f 100644
--- a/flang/lib/Semantics/CMakeLists.txt
+++ b/flang/lib/Semantics/CMakeLists.txt
@@ -53,6 +53,15 @@ add_flang_library(FortranSemantics
   type.cpp
   unparse-with-symbols.cpp
 
+  DISABLE_PCH_REUSE
+  PRECOMPILE_HEADERS
+  [["flang/Semantics/semantics.h"]]
+  [["flang/Semantics/type.h"]]
+  [["flang/Semantics/openmp-modifiers.h"]]
+  [["flang/Semantics/expression.h"]]
+  [["flang/Semantics/tools.h"]]
+  [["flang/Semantics/symbol.h"]]
+
   DEPENDS
   acc_gen
   omp_gen
@@ -68,12 +77,3 @@ add_flang_library(FortranSemantics
   FrontendOpenACC
   TargetParser
 )
-
-target_precompile_headers(FortranSemantics PRIVATE
-  [["flang/Semantics/semantics.h"]]
-  [["flang/Semantics/type.h"]]
-  [["flang/Semantics/openmp-modifiers.h"]]
-  [["flang/Semantics/expression.h"]]
-  [["flang/Semantics/tools.h"]]
-  [["flang/Semantics/symbol.h"]]
-)
diff --git a/llvm/CMakeLists.txt b/llvm/CMakeLists.txt
index 9954053591d30..a539a2182efa5 100644
--- a/llvm/CMakeLists.txt
+++ b/llvm/CMakeLists.txt
@@ -313,6 +313,22 @@ if(LLVM_CCACHE_BUILD)
         set(CCACHE_PROGRAM "CCACHE_DIR=${LLVM_CCACHE_DIR} ${CCACHE_PROGRAM}")
       endif()
       set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_PROGRAM})
+
+      if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+        # ccache with PCH can lead to false-positives when only a macro
+        # definition changes with non-Clang compilers, because macro definitions
+        # are not compared in preprocessed mode.
+        # See: https://github.com/ccache/ccache/issues/1668
+        if(NOT DEFINED CMAKE_DISABLE_PRECOMPILE_HEADERS)
+          message(NOTICE "Using ccache with precompiled headers with non-Clang "
+            "compilers is not supported. CMAKE_DISABLE_PRECOMPILE_HEADERS will be set to ON. "
+            "Pass -DCMAKE_DISABLE_PRECOMPILE_HEADERS=OFF to override this.")
+          set(CMAKE_DISABLE_PRECOMPILE_HEADERS "ON")
+        elseif(NOT CMAKE_DISABLE_PRECOMPILE_HEADERS)
+          message(WARNING "Using ccache with precompiled headers with non-Clang "
+            "compilers is not supported.")
+        endif()
+      endif()
     else()
       # Until a way to reliably configure ccache on Windows is found,
       # disable precompiled headers for Windows + ccache builds
diff --git a/llvm/cmake/modules/AddLLVM.cmake b/llvm/cmake/modules/AddLLVM.cmake
index d938214f9d0df..c9ed03d98cc9c 100644
--- a/llvm/cmake/modules/AddLLVM.cmake
+++ b/llvm/cmake/modules/AddLLVM.cmake
@@ -79,6 +79,48 @@ function(llvm_update_compile_flags name)
   target_compile_definitions(${name} PRIVATE ${LLVM_COMPILE_DEFINITIONS})
 endfunction()
 
+function(llvm_update_pch name)
+  if(LLVM_REQUIRES_RTTI OR LLVM_REQUIRES_EH)
+    # Non-default RTTI/EH results in incompatible flags, precluding PCH reuse.
+    set(ARG_DISABLE_PCH_REUSE ON)
+  endif()
+
+  # Find PCH with highest priority from dependencies. We reuse the first PCH
+  # with the highest priority. If the target has its own set of PCH, we give it
+  # a higher priority so that dependents will prefer the new PCH. We don't do
+  # transitive PCH reuse, because this causes too many unrelated naming
+  # collisions (e.g., in A -> B -> C{pch}, only B would reuse the PCH of C).
+  set(pch_priority 0)
+  llvm_map_components_to_libnames(libs
+    ${LLVM_LINK_COMPONENTS}
+  )
+  list(APPEND libs ${ARG_LINK_LIBS})
+  foreach(lib ${libs})
+    if(TARGET ${lib})
+      get_target_property(lib_pch_priority ${lib} LLVM_PCH_PRIORITY)
+      if(${lib_pch_priority} GREATER ${pch_priority})
+        set(pch_priority ${lib_pch_priority})
+        set(pch_reuse ${lib})
+      endif()
+    endif()
+  endforeach()
+
+  if(ARG_PRECOMPILE_HEADERS)
+    message(DEBUG "Adding PCH ${ARG_PRECOMPILE_HEADERS} for ${name} (prio ${pch_priority})")
+    target_precompile_headers(${name} PRIVATE $<$<COMPILE_LANGUAGE:CXX>:${ARG_PRECOMPILE_HEADERS}>)
+    if(NOT ARG_DISABLE_PCH_REUSE)
+      # Set priority so that dependants can reuse the PCH.
+      math(EXPR pch_priority "${pch_priority} + 1")
+      set_target_properties(${name} PROPERTIES LLVM_PCH_PRIORITY ${pch_priority})
+    endif()
+  elseif(pch_reuse AND NOT ARG_DISABLE_PCH_REUSE)
+    message(DEBUG "Using PCH ${pch_reuse} for ${name} (prio ${pch_priority})")
+    target_precompile_headers(${name} REUSE_FROM ${pch_reuse})
+  else()
+    message(DEBUG "Using NO PCH for ${name}")
+  endif()
+endfunction()
+
 function(add_llvm_symbol_exports target_name export_file)
   if("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin")
     set(native_export_file "${target_name}.exports")
@@ -485,6 +527,13 @@ endfunction(set_windows_version_resource_properties)
 #     Corresponds to OUTPUT_NAME in target properties.
 #   DEPENDS targets...
 #     Same semantics as add_dependencies().
+#   PRECOMPILE_HEADERS include_directives...
+#     Pre-compiled C++ headers to use. PCH can be reused by dependants. If
+#     specified, no PCHs from dependencies will be reused.
+#   DISABLE_PCH_REUSE
+#     Disable reuse of pre-compiled headers in both directions: the library will
+#     not reuse the PCH of a dependency and a defined PCH will not be offered
+#     for reuse by dependants.
 #   LINK_COMPONENTS components...
 #     Same as the variable LLVM_LINK_COMPONENTS.
 #   LINK_LIBS lib_targets...
@@ -504,11 +553,12 @@ endfunction(set_windows_version_resource_properties)
 #   )
 function(llvm_add_library name)
   cmake_parse_arguments(ARG
-    "MODULE;SHARED;STATIC;OBJECT;DISABLE_LLVM_LINK_LLVM_DYLIB;SONAME;NO_INSTALL_RPATH;COMPONENT_LIB"
+    "MODULE;SHARED;STATIC;OBJECT;DISABLE_LLVM_LINK_LLVM_DYLIB;SONAME;NO_INSTALL_RPATH;COMPONENT_LIB;DISABLE_PCH_REUSE"
     "OUTPUT_NAME;PLUGIN_TOOL;ENTITLEMENTS;BUNDLE_PATH"
-    "ADDITIONAL_HEADERS;DEPENDS;LINK_COMPONENTS;LINK_LIBS;OBJLIBS"
+    "ADDITIONAL_HEADERS;PRECOMPILE_HEADERS;DEPENDS;LINK_COMPONENTS;LINK_LIBS;OBJLIBS"
     ${ARGN})
   list(APPEND LLVM_COMMON_DEPENDS ${ARG_DEPENDS})
+  list(APPEND LLVM_LINK_COMPONENTS ${ARG_LINK_COMPONENTS})
   if(ARG_ADDITIONAL_HEADERS)
     # Pass through ADDITIONAL_HEADERS.
     set(ARG_ADDITIONAL_HEADERS ADDITIONAL_HEADERS ${ARG_ADDITIONAL_HEADERS})
@@ -550,6 +600,7 @@ function(llvm_add_library name)
       ${ALL_FILES}
       )
     llvm_update_compile_flags(${obj_name})
+    llvm_update_pch(${obj_name})
     if(CMAKE_GENERATOR STREQUAL "Xcode")
       set(DUMMY_FILE ${CMAKE_CURRENT_BINARY_DIR}/Dummy.c)
       file(WRITE ${DUMMY_FILE} "// This file intentionally empty\n")
@@ -604,7 +655,7 @@ function(llvm_add_library name)
       ${output_name}
       OBJLIBS ${ALL_FILES} # objlib
       LINK_LIBS ${ARG_LINK_LIBS}
-      LINK_COMPONENTS ${ARG_LINK_COMPONENTS}
+      LINK_COMPONENTS ${LLVM_LINK_COMPONENTS}
       )
     set_target_properties(${name_static} PROPERTIES FOLDER "${subproject_title}/Libraries")
 
@@ -676,6 +727,11 @@ function(llvm_add_library name)
   # $<TARGET_OBJECTS> doesn't require compile flags.
   if(NOT obj_name)
     llvm_update_compile_flags(${name})
+    llvm_update_pch(${name})
+  else()
+    target_precompile_headers(${name} REUSE_FROM ${obj_name})
+    get_target_property(pch_priority ${obj_name} LLVM_PCH_PRIORITY)
+    set_target_properties(${name} PROPERTIES LLVM_PCH_PRIORITY ${pch_priority})
   endif()
   add_link_opts( ${name} )
   if(ARG_OUTPUT_NAME)
@@ -758,8 +814,7 @@ function(llvm_add_library name)
         target_compile_definitions(${name} PRIVATE LLVM_BUILD_STATIC)
       endif()
       llvm_map_components_to_libnames(llvm_libs
-       ${ARG_LINK_COMPONENTS}
-       ${LLVM_LINK_COMPONENTS}
+        ${LLVM_LINK_COMPONENTS}
        )
     endif()
   else()
@@ -770,7 +825,7 @@ function(llvm_add_library name)
     # It would be nice to verify that we have the dependencies for this library
     # name, but using get_property(... SET) doesn't suffice to determine if a
     # property has been set to an empty value.
-    set_property(TARGET ${name} PROPERTY LLVM_LINK_COMPONENTS ${ARG_LINK_COMPONENTS} ${LLVM_LINK_COMPONENTS})
+    set_property(TARGET ${name} PROPERTY LLVM_LINK_COMPONENTS ${LLVM_LINK_COMPONENTS})
 
     # This property is an internal property only used to make sure the
     # link step applied in LLVMBuildResolveComponentsLink uses the same
@@ -993,6 +1048,7 @@ macro(generate_llvm_objects name)
       ${ALL_FILES}
       )
     llvm_update_compile_flags(${obj_name})
+    llvm_update_pch(${obj_name})
     set(ALL_FILES "$<TARGET_OBJECTS:${obj_name}>")
     if(ARG_DEPENDS)
       add_dependencies(${obj_name} ${ARG_DEPENDS})
@@ -1032,7 +1088,7 @@ endmacro()
 
 macro(add_llvm_executable name)
   cmake_parse_arguments(ARG
-    "DISABLE_LLVM_LINK_LLVM_DYLIB;IGNORE_EXTERNALIZE_DEBUGINFO;NO_INSTALL_RPATH;SUPPORT_PLUGINS;EXPORT_SYMBOLS"
+    "DISABLE_LLVM_LINK_LLVM_DYLIB;IGNORE_EXTERNALIZE_DEBUGINFO;NO_INSTALL_RPATH;SUPPORT_PLUGINS;EXPORT_SYMBOLS;DISABLE_PCH_REUSE"
     "ENTITLEMENTS;BUNDLE_PATH"
     ""
     ${ARGN})
@@ -1073,6 +1129,11 @@ macro(add_llvm_executable name)
   # $<TARGET_OBJECTS> doesn't require compile flags.
   if(NOT LLVM_ENABLE_OBJLIB)
     llvm_update_compile_flags(${name})
+    llvm_update_pch(${name})
+  else()
+    target_precompile_headers(${name} REUSE_FROM ${obj_name})
+    get_target_property(pch_priority ${obj_name} LLVM_PCH_PRIORITY)
+    set_target_properties(${name} PROPERTIES LLVM_PCH_PRIORITY ${pch_priority})
   endif()
 
   if (ARG_SUPPORT_PLUGINS AND NOT "${CMAKE_SYSTEM_NAME}" MATCHES "AIX")
@@ -1780,7 +1841,7 @@ function(add_unittest test_suite test_name)
   endif()
 
   list(APPEND LLVM_LINK_COMPONENTS Support) # gtest needs it for raw_ostream
-  add_llvm_executable(${test_name} IGNORE_EXTERNALIZE_DEBUGINFO NO_INSTALL_RPATH ${ARGN})
+  add_llvm_executable(${test_name} IGNORE_EXTERNALIZE_DEBUGINFO NO_INSTALL_RPATH DISABLE_PCH_REUSE ${ARGN})
   get_subproject_title(subproject_title)
   set_target_properties(${test_name} PROPERTIES FOLDER "${subproject_title}/Tests/Unit")
 
diff --git a/llvm/cmake/modules/HandleLLVMOptions.cmake b/llvm/cmake/modules/HandleLLVMOptions.cmake
index 311123084bf58..2e90c0ad0ad76 100644
--- a/llvm/cmake/modules/HandleLLVMOptions.cmake
+++ b/llvm/cmake/modules/HandleLLVMOptions.cmake
@@ -1304,6 +1304,27 @@ if (LLVM_BUILD_INSTRUMENTED AND LLVM_BUILD_INSTRUMENTED_COVERAGE)
   message(FATAL_ERROR "LLVM_BUILD_INSTRUMENTED and LLVM_BUILD_INSTRUMENTED_COVERAGE cannot both be specified")
 endif()
 
+if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT DEFINED CMAKE_DISABLE_PRECOMPILE_HEADERS)
+  # Pre-compiled headers with GCC (tested versions 14+15) provide very little
+  # compile-time improvements, but substantially increase the build dir size.
+  # Therefore, disable PCH with GCC by default.
+  message(NOTICE "Precompiled headers are disabled by default with GCC. "
+    "Pass -DCMAKE_DISABLE_PRECOMPILE_HEADERS=OFF to override.")
+  set(CMAKE_DISABLE_PRECOMPILE_HEADERS ON)
+endif()
+if(NOT CMAKE_DISABLE_PRECOMPILE_HEADERS)
+  message(STATUS "Precompiled headers enabled.")
+  if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+    # Clang requires this flag in order for precompiled headers to work with ccache
+    append("-Xclang -fno-pch-timestamp" CMAKE_CXX_FLAGS)
+  elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+    # GCC requires this flag in order for precompiled headers to work with ccache
+    append("-fpch-preprocess" CMAKE_CXX_FLAGS)
+  endif()
+else()
+  message(STATUS "Precompiled headers disabled.")
+endif()
+
 set(LLVM_THINLTO_CACHE_PATH "${PROJECT_BINARY_DIR}/lto.cache" CACHE STRING "Set ThinLTO cache path. This can be used when building LLVM from several different directiories.")
 
 if(LLVM_ENABLE_LTO AND WIN32 AND NOT LINKER_IS_LLD_LINK AND NOT MINGW)
diff --git a/llvm/examples/OrcV2Examples/OrcV2CBindingsAddObjectFile/CMakeLists.txt b/llvm/examples/OrcV2Examples/OrcV2CBindingsAddObjectFile/CMakeLists.txt
index cc50112f326ea..9a82cc0ed0916 100644
--- a/llvm/examples/OrcV2Examples/OrcV2CBindingsAddObjectFile/CMakeLists.txt
+++ b/llvm/examples/OrcV2Examples/OrcV2CBindingsAddObjectFile/CMakeLists.txt
@@ -12,4 +12,6 @@ set(LLVM_LINK_COMPONENTS
 
 add_llvm_example(OrcV2CBindingsAddObjectFile
   OrcV2CBindingsAddObjectFile.c
+
+  DISABLE_PCH_REUSE # no C++ files, prevent CMake error.
   )
diff --git a/llvm/examples/OrcV2Examples/OrcV2CBindingsBasicUsage/CMakeLists.txt b/llvm/examples/OrcV2Examples/OrcV2CBindingsBasicUsage/CMakeLists.txt
index 0f18d6c24912f..1a738f60d15e1 100644
--- a/llvm/examples/OrcV2Examples/OrcV2CBindingsBasicUsage/CMakeLists.txt
+++ b/llvm/examples/OrcV2Examples/OrcV2CBindingsBasicUsage/CMakeLists.txt
@@ -12,4 +12,6 @@ set(LLVM_LINK_COMPONENTS
 
 add_llvm_example(OrcV2CBindingsBasicUsage
   OrcV2CBindingsBasicUsage.c
+
+  DISABLE_PCH_REUSE # no C++ files, prevent CMake error.
   )
diff --git a/llvm/examples/OrcV2Examples/OrcV2CBindingsDumpObjects/CMakeLists.txt b/llvm/examples/OrcV2Examples/OrcV2CBindingsDumpObjects/CMakeLists.txt
index 8e2c97d782062..c7f748a12bf35 100644
--- a/llvm/examples/OrcV2Examples/OrcV2CBindingsDumpObjects/CMakeLists.txt
+++ b/llvm/examples/OrcV2Examples/OrcV2CBindingsDumpObjects/CMakeLists.txt
@@ -12,4 +12,6 @@ set(LLVM_LINK_COMPONENTS
 
 add_llvm_example(OrcV2CBindingsDumpObjects
   OrcV2CBindingsDumpObjects.c
+
+  DISABLE_PCH_REUSE # no C++ files, prevent CMake error.
   )
diff --git a/llvm/examples/OrcV2Examples/OrcV2CBindingsIRTransforms/CMakeLists.txt b/llvm/examples/OrcV2Examples/OrcV2CBindingsIRTransforms/CMakeLists.txt
index af1b43e43e111..b152faf1e3527 100644
--- a/llvm/examples/OrcV2Examples/OrcV2CBindingsIRTransforms/CMakeLists.txt
+++ b/llvm/examples/OrcV2Examples/OrcV2CBindingsIRTransforms/CMakeLists.txt
@@ -13,4 +13,6 @@ set(LLVM_LINK_COMPONENTS
 
 add_llvm_example(OrcV2CBindingsIRTransforms
   OrcV2CBindingsIRTransforms.c
+
+  DISABLE_PCH_REUSE # no C++ files, prevent CMake error.
   )
diff --git a/llvm/examples/OrcV2Examples/OrcV2CBindingsLazy/CMakeLists.txt b/llvm/examples/OrcV2Examples/OrcV2CBindingsLazy/CMakeLists.txt
index 52eb2d496fc23..c46700d9b2576 100644
--- a/llvm/examples/OrcV2Examples/OrcV2CBindingsLazy/CMakeLists.txt
+++ b/llvm/examples/OrcV2Examples/OrcV2CBindingsLazy/CMakeLists.txt
@@ -12,4 +12,6 @@ set(LLVM_LINK_COMPONENTS
 
 add_llvm_example(OrcV2CBindingsLazy
   OrcV2CBindingsLazy.c
+
+  DISABLE_PCH_REUSE # no C++ files, prevent CMake error.
   )
diff --git a/llvm/examples/OrcV2Examples/OrcV2CBindingsMCJITLikeMemoryManager/CMakeLists.txt b/llvm/examples/OrcV2Examples/OrcV2CBindingsMCJITLikeMemoryManager/CMakeLists.txt
index 4cdc2b114e9a5..d4a06104120c8 100644
--- a/llvm/examples/OrcV2Examples/OrcV2CBindingsMCJITLikeMemoryManager/CMakeLists.txt
+++ b/llvm/examples/OrcV2Examples/OrcV2CBindingsMCJITLikeMemoryManager/CMakeLists.txt
@@ -12,4 +12,6 @@ set(LLVM_LINK_COMPONENTS
 
 add_llvm_exampl...
[truncated]

@aengelke
Copy link
Contributor Author

@boomanaiden154 Regarding the pre-commit CI: sccache appears to not support PCH and I think it would be a good idea to disable PCH in the pre-commit CI to catch missing includes. I have no idea where the configuration lives and what I would need to do to pass -DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON, can you give some pointers here?

The PR currently disables PCH for GCC, which means that post-commit builders will implicitly have coverage for both PCH and non-PCH builds.

@aengelke
Copy link
Contributor Author

c-t-t, in total -15% instructions:u/wall-time on stage2-clang.

@boomanaiden154
Copy link
Contributor

boomanaiden154 Regarding the pre-commit CI: sccache appears to not support PCH and I think it would be a good idea to disable PCH in the pre-commit CI to catch missing includes. I have no idea where the configuration lives and what I would need to do to pass -DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON, can you give some pointers here?

I thought we already had it disabled, but it looks like we do not. The config is here

cmake -S "${MONOREPO_ROOT}"/llvm -B "${BUILD_DIR}" \
.

There's also a monolithic-windows.sh on the Windows side that you might need to touch given we use clang-cl over there, but not exactly sure how that interacts.

@vbvictor
Copy link
Contributor

Regarding the pre-commit CI: sccache appears to not support PCH and I think it would be a good idea to disable PCH in the pre-commit CI to catch missing includes.

Can we instead set CMAKE_DISABLE_PRECOMPILE_HEADERS=ON inside llvm/CMakeLists.txt if we see that
CMAKE_CXX_COMPILER_LAUNCHER is set too sccache?
I use sccache locally, and currently, I will need to set DISABLE_PRECOMPILE_HEADERS manually, right? I think we should give a warning message if sccache + pch is activated.

@kuhar kuhar requested a review from makslevental January 16, 2026 18:13
@makslevental
Copy link
Contributor

Regarding the pre-commit CI: sccache appears to not support PCH and I think it would be a good idea to disable PCH in the pre-commit CI to catch missing includes.

Can we instead set CMAKE_DISABLE_PRECOMPILE_HEADERS=ON inside llvm/CMakeLists.txt if we see that CMAKE_CXX_COMPILER_LAUNCHER is set too sccache? I use sccache locally, and currently, I will need to set DISABLE_PRECOMPILE_HEADERS manually, right? I think we should give a warning message if sccache + pch is activated.

I would prefer not to make this automatic - we have enough "hand-holding/automatic" such variables which cause problems eventually because they wholesale force some behavior for all targets and all users. A warning is fine.

Copy link
Contributor

@makslevental makslevental left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just my 2 cents: The CMake-isms look completely reasonable but I'll come back to take a closer look soon. But thank you for working on this - a %1 improvement in LLVM compile time probably saves 1000 trees per year or something like that 😄.

#include "llvm/ADT/Statistic.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did you decide on these exact includes? I think MathExtras and SmallVectorExtras are also popular.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at the output of -ftime-trace of a full Clang+LLVM build, sorted by time spent in parsing (including transitively included headers), and selected headers that showed up high in that list. This is obviously not accurate, but was sufficient for the initial RFC. That's also how headers like JSON ended up in the list: it isn't used much, but it takes ~180ms to parse+instantiate templates (although I'm unsure whether it's a net win to have it in the PCH).

The includes of Clang+LLVM by counts (cut off at 100, output by ninja -t deps) and current PCH status is below. The numbers can, however, be slightly misleading: llvm/ADT/ilist_node.h is used 2631 times, but in >=2205 cases, this will be pulled in by Instruction.h, which will get part of the llvm/IR PCH. (NB: MathExtras is currently transitively included, SmallVectorExtras is only used in 6 CUs in Clang+LLVM.)

We can certainly adjust the list, consider this to be a not-particularly-optimized initial proposal. I'm not sure if we want to invest a lot of time in tooling to determine an ideal PCH set, however, as it is a very complex and multi-dimensional optimization problem.

   5322 PCH llvm/Support/Compiler.h
   5280 PCH llvm/ADT/STLForwardCompat.h
   5275 PCH llvm/ADT/STLFunctionalExtras.h
   5275 PCH llvm/ADT/DenseMapInfo.h
   5275 PCH llvm/ADT/ADL.h
   5273 PCH llvm/ADT/iterator_range.h
   5265 PCH llvm/Support/DataTypes.h
   5251 PCH llvm/ADT/SmallVector.h
   5250 PCH llvm/Support/ErrorHandling.h
   5245 PCH llvm/ADT/bit.h
   5239 PCH llvm/Support/type_traits.h
   5232 PCH llvm/ADT/StringRef.h
   5229 PCH llvm/Support/SwapByteOrder.h
   5228 PCH llvm/ADT/Hashing.h
   5220 PCH llvm/ADT/iterator.h
   5218 PCH llvm/ADT/STLExtras.h
   5121 PCH llvm/Support/MathExtras.h
   5103 PCH llvm/Support/raw_ostream.h
   5048 PCH llvm/ADT/ArrayRef.h
   4960 PCH llvm/Support/MemAlloc.h
   4925 PCH llvm/Support/PointerLikeTypeTraits.h
   4822 PCH llvm/Support/ReverseIteration.h
   4821 PCH llvm/ADT/EpochTracker.h
   4815 PCH llvm/Support/AlignOf.h
   4797 PCH llvm/ADT/Twine.h
   4797 PCH llvm/ADT/DenseMap.h
   4660 PCH llvm/ADT/SmallString.h
   4572 PCH llvm/Support/Alignment.h
   4557 PCH llvm/Support/ErrorOr.h
   4536 PCH llvm/Support/Debug.h
   4506 PCH llvm/Support/Format.h
   4490 PCH llvm/Support/AllocatorBase.h
   4402 PCH llvm/Support/Error.h
   4381 PCH llvm/Support/Casting.h
   4229 PCH llvm/ADT/StringMapEntry.h
   4218 PCH llvm/Support/float128.h
   4218 PCH llvm/ADT/APInt.h
   4195 PCH llvm/ADT/StringMap.h
   4102 PCH llvm/Support/Allocator.h
   4093 PCH llvm/ADT/StringSwitch.h
   4036 PCH llvm/ADT/BitmaskEnum.h
   4004 PCH llvm/ADT/DenseSet.h
   3893 PCH llvm/Support/CBindingWrapping.h
   3870     llvm/ADT/PointerIntPair.h
   3850 PCH llvm/Support/VersionTuple.h
   3811 PCH llvm/ADT/APSInt.h
   3670 PCH llvm/ADT/StringExtras.h
   3587     llvm/ADT/PointerUnion.h
   3479     llvm/Support/CodeGen.h
   3393 PCH llvm/ADT/SmallPtrSet.h
   3231 PCH llvm/ADT/FloatingPointMode.h
   3214     llvm/Support/TypeSize.h
   3205     llvm/ADT/Sequence.h
   3125 PCH llvm/ADT/BitVector.h
   3110 PCH llvm/Support/Endian.h
   3085     llvm/Support/TrailingObjects.h
   3052 PCH llvm/Support/SMLoc.h
   3051     llvm/Support/AtomicOrdering.h
   3050     llvm/ADT/MapVector.h
   2913 PCH llvm/ADT/APFloat.h
   2865 PCH llvm/Support/MemoryBufferRef.h
   2833     llvm/ADT/TinyPtrVector.h
   2812 PCH llvm/Support/MemoryBuffer.h
   2807 PCH llvm/Support/StringSaver.h
   2760 PCH llvm/ADT/IntrusiveRefCntPtr.h
   2675 PCH llvm/ADT/SetVector.h
   2640     llvm/ADT/StringTable.h
   2633     llvm/ADT/ilist_node_base.h
   2631     llvm/ADT/ilist_node_options.h
   2631     llvm/ADT/ilist_node.h
   2621     llvm/ADT/ilist_base.h
   2620     llvm/ADT/simple_ilist.h
   2620     llvm/ADT/ilist_iterator.h
   2604     llvm/Support/Compression.h
   2593     llvm/ADT/ilist.h
   2501     llvm/ADT/GraphTraits.h
   2480     llvm/ADT/FunctionExtras.h
   2383 PCH llvm/Support/CommandLine.h
   2370 PCH llvm/Support/FormatVariadicDetails.h
   2289     llvm/Support/xxhash.h
   2273 PCH llvm/Support/SourceMgr.h
   2272     llvm/ADT/Bitfields.h
   2247     llvm/Support/ModRef.h
   2206     llvm/ADT/StringSet.h
   2201     llvm/Support/Threading.h
   2105 PCH llvm/Support/MD5.h
   2105     llvm/ADT/FoldingSet.h
   1939     llvm/Support/TypeName.h
   1777     llvm/Support/RWMutex.h
   1763 PCH llvm/Support/FileSystem/UniqueID.h
   1753 PCH llvm/Support/NativeFormatting.h
   1748 PCH llvm/Support/FormatProviders.h
   1601     llvm/Support/Printable.h
   1590     llvm/Support/Recycler.h
   1570     llvm/Support/BranchProbability.h
   1557     llvm/Support/Discriminator.h
   1529     llvm/ADT/SmallSet.h
   1395 PCH llvm/Support/Chrono.h
   1386 PCH llvm/Support/FileSystem.h
   1299     llvm/Support/UniqueBBID.h
   1293     llvm/Support/Path.h
   1276     llvm/Support/RecyclingAllocator.h
   1260     llvm/ADT/SparseBitVector.h
   1210     llvm/Support/TargetOpcodes.def
   1209     llvm/ADT/PointerSumType.h
   1207     llvm/Support/ArrayRecycler.h
   1189     llvm/Support/DXILABI.h
   1171     llvm/Support/HashBuilder.h
   1151     llvm/Support/AllocToken.h
   1109     llvm/Support/Errc.h
   1078     llvm/ADT/CachedHashString.h
   1074     llvm/Support/ExtensibleRTTI.h
   1040     llvm/Support/PrettyStackTrace.h
   1038     llvm/ADT/Uniformity.h
   1011     llvm/Support/raw_os_ostream.h
    956     llvm/ADT/IndexedMap.h
    951     llvm/Support/AMDGPUAddrSpace.h
    951     llvm/ADT/GenericSSAContext.h
    942     llvm/ADT/GenericCycleInfo.h
    939     llvm/Support/PGOOptions.h
    916     llvm/Support/Mutex.h
    916     llvm/ADT/APFixedPoint.h
    899     llvm/Support/VirtualFileSystem.h
    885     llvm/ADT/StableHashing.h
    842     llvm/ADT/SmallBitVector.h
    804     llvm/ADT/Bitset.h
    801     llvm/Support/Regex.h
    788     llvm/Support/KnownFPClass.h
    756     llvm/ADT/DepthFirstIterator.h
    717     llvm/ADT/PagedVector.h
    694     llvm/Support/GenericDomTree.h
    694     llvm/Support/CFGUpdate.h
    694     llvm/Support/CFGDiff.h
    693     llvm/Support/DynamicLibrary.h
    688     llvm/Support/BlockFrequency.h
    613 PCH llvm/Support/FormatVariadic.h
    613 PCH llvm/Support/FormatCommon.h
    597     llvm/Support/Registry.h
    552     llvm/Support/Hash.h
    476     llvm/ADT/PostOrderIterator.h
    471     llvm/ADT/IntervalMap.h
    450 PCH llvm/Support/YAMLParser.h
    443     llvm/Support/ConvertUTF.h
    434 PCH llvm/Support/YAMLTraits.h
    433 PCH llvm/ADT/Statistic.h
    414     llvm/Support/FormattedStream.h
    398     llvm/ADT/SetOperations.h
    395     llvm/Support/ELFAttributes.h
    393     llvm/Support/BinaryStreamError.h
    392     llvm/Support/BinaryStream.h
    378     llvm/Support/GenericLoopInfo.h
    353     llvm/Support/KnownBits.h
    340     llvm/Support/InstructionCost.h
    340     llvm/Support/BinaryStreamRef.h
    335     llvm/Support/BinaryStreamArray.h
    332     llvm/Support/BinaryStreamReader.h
    321     llvm/Support/ARMBuildAttributes.h
    320     llvm/Support/WithColor.h
    308     llvm/Support/GenericLoopInfoImpl.h
    301     llvm/Support/Timer.h
    285     llvm/Support/SaveAndRestore.h
    273     llvm/Support/FileCollector.h
    270     llvm/Support/Signals.h
    268     llvm/Support/VirtualOutputConfig.h
    268     llvm/Support/VirtualOutputConfig.def
    266     llvm/Support/VirtualOutputFile.h
    264     llvm/Support/VirtualOutputBackend.h
    262     llvm/Support/BuryPointer.h
    257     llvm/Support/DataExtractor.h
    252     llvm/Support/Program.h
    243 PCH llvm/Support/JSON.h
    232     llvm/Support/BinaryStreamWriter.h
    217     llvm/ADT/ImmutableSet.h
    213     llvm/Support/Memory.h
    206     llvm/ADT/ImmutableMap.h
    202     llvm/ADT/ImmutableList.h
    186     llvm/Support/MSVCErrorWorkarounds.h
    185     llvm/Support/TargetSelect.h
    184     llvm/ADT/Any.h
    172     llvm/Testing/Support/SupportHelpers.h
    170     llvm/Support/EndianStream.h
    169     llvm/Support/FileOutputBuffer.h
    164     llvm/Support/ManagedStatic.h
    163     llvm/Support/LEB128.h
    157     llvm/ADT/SparseSet.h
    155     llvm/Support/Process.h
    154     llvm/Support/ScopedPrinter.h
    150     llvm/Support/BinaryByteStream.h
    150     llvm/ADT/fallible_iterator.h
    149     llvm/Testing/Support/Error.h
    147     llvm/ADT/EquivalenceClasses.h
    107     llvm/Support/CheckedArithmetic.h
    103     llvm/ADT/SparseMultiSet.h
    100     llvm/ADT/IntEqClasses.h

Copy link

@sharkautarch sharkautarch Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could try using ClangBuildAnalyzer
which simply analyzes the files created when compiling w/ -ftime-trace, and reports the top N longest-to-parse headers, the top N most included headers, the top N longest-to-instatiate templates, etc.

Apparently, it also by default only includes the root-included headers in the report. That is to say, by default, it excludes header inclusions that were first included by another header.
This may help with figuring out which headers should be prioritized over others.

@llvmbot llvmbot added the infrastructure Bugs about LLVM infrastructure label Jan 17, 2026
@aengelke
Copy link
Contributor Author

I think we should give a warning message if sccache + pch is activated.

Added warnings for typical CMAKE_CXX_COMPILER_LAUNCHER values.

how do we enable PCH? Is there some standard cmake flag or an llvm-specific one? Could you include this in the PR description?

Added a note that PCH is on-by-default unless noted as exception.

Err, how is it going to interact with downstream projects that don't disable PCH for C files?

If the downstream just uses standard CMake add_library etc., nothing changes. If the downstream uses AddLLVM (e.g., add_llvm_library) for a C-only target and doesn't specify DISABLE_PCH_REUSE, then CMake will fail with an error similar to:

CMake Error in examples/OrcV2Examples/OrcV2CBindingsBasicUsage/CMakeLists.txt:
  Unable to resolve full path of PCH-header
  '/path/to/llvm/build/lib/Support/CMakeFiles/LLVMSupport.dir/cmake_pch.h'
  assigned to target OrcV2CBindingsBasicUsage, although its path is supposed
  to be known!

@github-actions
Copy link

github-actions bot commented Jan 17, 2026

🐧 Linux x64 Test Results

  • 196330 tests passed
  • 6419 tests skipped

✅ The build succeeded and all tests passed.

aengelke added a commit to aengelke/llvm-project that referenced this pull request Jan 17, 2026
Spliced out from llvm#176420 to make sure that CI is fine without PCH.
@aengelke
Copy link
Contributor Author

Re build failure: it turns out that CMake disables warnings in PCH by default, I filed #176566 to work around this.

@nikic
Copy link
Contributor

nikic commented Jan 17, 2026

Should I pass -DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON on the compile time tracker so that we still become aware of expensive includes being added to core headers? (On the other hand, that makes it harder to tune the PCH configuration.)

@nikic
Copy link
Contributor

nikic commented Jan 17, 2026

Regarding the pre-commit CI: sccache appears to not support PCH and I think it would be a good idea to disable PCH in the pre-commit CI to catch missing includes.

Can we instead set CMAKE_DISABLE_PRECOMPILE_HEADERS=ON inside llvm/CMakeLists.txt if we see that CMAKE_CXX_COMPILER_LAUNCHER is set too sccache? I use sccache locally, and currently, I will need to set DISABLE_PRECOMPILE_HEADERS manually, right? I think we should give a warning message if sccache + pch is activated.

I would prefer not to make this automatic - we have enough "hand-holding/automatic" such variables which cause problems eventually because they wholesale force some behavior for all targets and all users. A warning is fine.

I'm unsure on this point. sccache is a common CI configuration, and nobody is going to notice those warnings. I guess this depends somewhat on what the failure mode for sccache + pch is. Build failures? Silent miscompiles? Silent failures to use the cache?

@makslevental
Copy link
Contributor

I'm unsure on this point. sccache is a common CI configuration, and nobody is going to notice those warnings. I guess this depends somewhat on what the failure mode for sccache + pch is. Build failures? Silent miscompiles? Silent failures to use the cache?

Fair enough. I was thining about this and probably this one is fine because PCH either works on your system or doesn't (it's the CMAKE_MODULE_LINKER_FLAGS-type flags that are a problem where it should be determined on a target by target basis).

@aengelke
Copy link
Contributor Author

aengelke commented Jan 17, 2026

Should I pass -DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON on the compile time tracker so that we still become aware of expensive includes being added to core headers?

I'm unsure, but would be inclined not to do so for the reason you mentioned. Ideally, of course, we would track both, but increasing iteration times from ~15 to ~21 minutes is probably unacceptable (and I'd personally see them much rather at 11 minutes).

Maybe we can use data from stage1 somehow? In principle, this should be possible: for every ccache miss, track time+max-rss, and aggregate with data from ccache hits. Unfortunately, I don't think this is implementable without ccache changes.

Alternatively, collect data not for every commit? That would probably be an architectural change.

sccache is a common CI configuration, and nobody is going to notice those warnings. I guess this depends somewhat on what the failure mode for sccache + pch is. Build failures? Silent miscompiles? Silent failures to use the cache?

As with ccache: miscompiles with GCC, ok with Clang (in my limited testing):

  • sccache will incorrectly identify the PCH as cache hit even if a macro definition changed, due to identical preprocessor output. I filed PCH cause false postive cache hits mozilla/sccache#2562.
  • This causes miscompiles for GCC. (edit to clarify: this PR proposes to disable PCH with GCC by default in all configurations anyway.)
  • But the results for Clang appear to be correct and there is the expected number of cache hits from source files. It appears that Clang doesn't include macro definitions in the PCH? At least in my tests, the PCH ended up being identical (same hash sum)? (@ChuanqiXu9 @Bigcheese is this correct?)

Inside CMake, we cannot reliably whether ccache or sccache is actually used (e.g., clang could actually be /usr/lib/ccache/clang), so any automation here is going to be brittle to some degree.

aengelke added a commit that referenced this pull request Jan 17, 2026
Spliced out from #176420 to make sure that CI is fine without PCH, which
are currently used by Flang.
Priyanshu3820 pushed a commit to Priyanshu3820/llvm-project that referenced this pull request Jan 18, 2026
Spliced out from llvm#176420 to make sure that CI is fine without PCH, which
are currently used by Flang.
BStott6 pushed a commit to BStott6/llvm-project that referenced this pull request Jan 22, 2026
Spliced out from llvm#176420 to make sure that CI is fine without PCH, which
are currently used by Flang.
Copy link
Contributor

@nikic nikic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

dictionary.c

# No C++ files, prevent CMake error.
DISABLE_PCH_REUSE
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder whether this is something we can detect and handle automatically?

@bangtianliu
Copy link
Contributor

We cherry-picked this PR for IREE CI test: iree-org/iree#23279. It seems that you need to add DISABLE_PCH_REUSE to more cases to handle the incompatibilities. Refer to iree-org@f474998 for our fixes.

@aengelke
Copy link
Contributor Author

@bangtianliu Thanks for testing. I fixed the combination with CMAKE_POSITION_INDEPENDENT_CODE=ON. I couldn't reproduce the error regarding visibility. Can you provide a LLVM CMake invocation that leads to this error?

@sharkautarch
Copy link

We cherry-picked this PR for IREE CI test: iree-org/iree#23279. It seems that you need to add DISABLE_PCH_REUSE to more cases to handle the incompatibilities. Refer to iree-org@f474998 for our fixes.

Instead of disabling pch for -fPIE executables, couldn’t you just add -fPIC to the flags?

@bangtianliu
Copy link
Contributor

bangtianliu commented Jan 27, 2026

@bangtianliu Thanks for testing. I fixed the combination with CMAKE_POSITION_INDEPENDENT_CODE=ON. I couldn't reproduce the error regarding visibility. Can you provide a LLVM CMake invocation that leads to this error?

should be here: https://github.com/iree-org/llvm-project/blob/1839675d168e357c3db1559446031d55833f0181/llvm/lib/Target/CMakeLists.txt#L25-L32

so should be -DBUILD_SHARED_LIBS=OFF

@aengelke
Copy link
Contributor Author

We already add -fPIC when LLVM_ENABLE_PIC is set (on by default). But CMAKE_POSITION_INDEPENDENT_CODE additionally adds -fPIE afterwards in a way that seems to be not suppressible via documented methods without making the resulting executable link without -pie. We could probably add -fPIC via target_compile_options instead of CMAKE_CXX_FLAGS, which should move it to the end, but this does seem very hacky... it would also break if LLVM_ENABLE_PIC is disabled.

@bangtianliu I mean a cmake command line invocation. BUILD_SHARED_LIBS is off by default. I can't reproduce a similar error on my system with Clang or GCC.

@github-actions
Copy link

github-actions bot commented Jan 27, 2026

🪟 Windows x64 Test Results

  • 135761 tests passed
  • 4450 tests skipped

✅ The build succeeded and all tests passed.

@bangtianliu
Copy link
Contributor

bangtianliu commented Jan 27, 2026

@bangtianliu I mean a cmake command line invocation. BUILD_SHARED_LIBS is off by default. I can't reproduce a similar error on my system with Clang or GCC.

@aengelke
I successfully reproduced the visibility error. The key is using Clang-14.

  cmake -GNinja -B build -S llvm \
   -DCMAKE_C_COMPILER=/usr/bin/clang \      # Must be clang-14
   -DCMAKE_CXX_COMPILER=/usr/bin/clang++ \  # Must be clang++-14
   -DLLVM_TARGETS_TO_BUILD="X86;AArch64" \
   -DBUILD_SHARED_LIBS=OFF \
   -DCMAKE_BUILD_TYPE=Release
 # Build a Target library (triggers the error)
 ninja lib/Target/X86/CMakeFiles/LLVMX86CodeGen.dir/X86AsmPrinter.cpp.o

Refer to https://gist.github.com/bangtianliu/eb23d95e72cd7b9787c9f8736f13817a for the log

@aengelke
Copy link
Contributor Author

Thanks for investigating. For me, Clang 17 is broken but Clang 18 works, so disabling for Clang <18 by default.

@ChuanqiXu9
Copy link
Member

  • But the results for Clang appear to be correct and there is the expected number of cache hits from source files. It appears that Clang doesn't include macro definitions in the PCH? At least in my tests, the PCH ended up being identical (same hash sum)? (@ChuanqiXu9 @Bigcheese is this correct?)

I think clang include the macro definition in the PCH. You can verify this by inserting outputs to ASTWriter::WritePreprocessor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cmake Build system in general and CMake in particular flang:driver flang:fir-hlfir flang:parser flang:semantics flang Flang issues not falling into any other category infrastructure Bugs about LLVM infrastructure llvm:support mlir testing-tools

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants