[clang] [llvm] [CMake] Make PCH reuse export-mode aware (PR #208235)
Fabrice de Gans via cfe-commits
cfe-commits at lists.llvm.org
Wed Jul 8 08:10:41 PDT 2026
https://github.com/Steelskin created https://github.com/llvm/llvm-project/pull/208235
A PCH bakes the `LLVM_ABI` export mode (dllexport/dllimport/none) into its AST. A consumer that uses `REUSE_FROM` a provider PCH built in a different mode results in a build or link failure.
* Teach `llvm_update_pch` to compute each target's mode and build its own PCH when it differs from the provider instead of reusing unconditionally.
* Gate the `LLVM_BUILD_STATIC` compile definition on `LLVM_ENABLE_LLVM_EXPORT_ANNOTATIONS`.
* Add an `LLVM_PCH_ABI_TAG` hook so clang can fold its own ABI dimension (`CLANG_BUILD_STATIC`) into the mode;
* Opt `llvm-config` out of PCH reuse. It defines `CMAKE_CFG_INTDIR` on its own command line.
>From 9f94f9edf4c37f29540f1b9dbc78c1b9889ae1e6 Mon Sep 17 00:00:00 2001
From: Fabrice de Gans <fabrice at thebrowser.company>
Date: Thu, 25 Jun 2026 11:45:34 +0200
Subject: [PATCH] [CMake] Make PCH reuse export-mode aware
A PCH bakes the `LLVM_ABI` export mode (dllexport/dllimport/none) into
its AST. A consumer that uses `REUSE_FROM` a provider PCH built in a
different mode results in a build or link failure.
* Teach `llvm_update_pch` to compute each target's mode and build
its own PCH when it differs from the provider instead of reusing
unconditionally.
* Gate the `LLVM_BUILD_STATIC` compile definition on
`LLVM_ENABLE_LLVM_EXPORT_ANNOTATIONS`.
* Add an `LLVM_PCH_ABI_TAG` hook so clang can fold its own ABI
dimension (`CLANG_BUILD_STATIC`) into the mode;
* Opt `llvm-config` out of PCH reuse. It defines `CMAKE_CFG_INTDIR`
on its own command line.
---
clang/cmake/modules/AddClang.cmake | 20 ++++++
llvm/cmake/modules/AddLLVM.cmake | 94 ++++++++++++++++++++++++---
llvm/tools/llvm-config/CMakeLists.txt | 4 ++
3 files changed, 108 insertions(+), 10 deletions(-)
diff --git a/clang/cmake/modules/AddClang.cmake b/clang/cmake/modules/AddClang.cmake
index e2112d7c326e2..40e070acac23b 100644
--- a/clang/cmake/modules/AddClang.cmake
+++ b/clang/cmake/modules/AddClang.cmake
@@ -106,7 +106,19 @@ macro(add_clang_library name)
endif()
set_property(GLOBAL APPEND PROPERTY CLANG_STATIC_LIBS ${name})
endif()
+ # Before creating the target, tag its clang ABI dimension so llvm_update_pch
+ # won't let it reuse an LLVM provider's PCH that was compiled without the
+ # clang ABI define this target carries.
+ set(LLVM_PCH_ABI_TAG "")
+ if(WIN32 AND NOT MINGW)
+ if(NOT CLANG_LINK_CLANG_DYLIB)
+ set(LLVM_PCH_ABI_TAG "clangstatic")
+ elseif(NOT ARG_SHARED AND NOT ARG_STATIC)
+ set(LLVM_PCH_ABI_TAG "clangexport")
+ endif()
+ endif()
llvm_add_library(${name} ${LIBTYPE} ${ARG_UNPARSED_ARGUMENTS} ${srcs})
+ unset(LLVM_PCH_ABI_TAG)
if((WIN32 AND NOT MINGW) AND NOT CLANG_LINK_CLANG_DYLIB)
# Make sure all consumers also turn off visibility macros so they're not
@@ -157,7 +169,15 @@ macro(add_clang_library name)
endmacro(add_clang_library)
macro(add_clang_executable name)
+ # Clang executables inherit CLANG_BUILD_STATIC transitively (PUBLIC) from the
+ # clang libraries they link, so tag their PCH mode the same way clang libraries
+ # are tagged (see LLVM_PCH_ABI_TAG in llvm_update_pch).
+ set(LLVM_PCH_ABI_TAG "")
+ if(WIN32 AND NOT MINGW AND NOT CLANG_LINK_CLANG_DYLIB)
+ set(LLVM_PCH_ABI_TAG "clangstatic")
+ endif()
add_llvm_executable( ${name} ${ARGN} )
+ unset(LLVM_PCH_ABI_TAG)
set_clang_windows_version_resource_properties(${name})
set_target_properties(${name} PROPERTIES XCODE_GENERATE_SCHEME ON)
endmacro(add_clang_executable)
diff --git a/llvm/cmake/modules/AddLLVM.cmake b/llvm/cmake/modules/AddLLVM.cmake
index a267166eb6c2d..399e8e50acabd 100644
--- a/llvm/cmake/modules/AddLLVM.cmake
+++ b/llvm/cmake/modules/AddLLVM.cmake
@@ -79,7 +79,11 @@ function(llvm_update_compile_flags name)
target_compile_definitions(${name} PRIVATE ${LLVM_COMPILE_DEFINITIONS})
endfunction()
-function(llvm_update_pch name)
+# `kind` is the target's category for export-mode purposes: lib, objlib or exe.
+function(llvm_update_pch name kind)
+ if(NOT kind)
+ set(kind "lib")
+ endif()
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)
@@ -107,6 +111,44 @@ function(llvm_update_pch name)
set(ARG_DISABLE_PCH_REUSE ON)
endif()
+ # Determine how LLVM_ABI resolves in this target's translation units, which
+ # determines PCH compatibility. This mirrors the LLVM_EXPORTS /
+ # LLVM_BUILD_STATIC compile definitions applied elsewhere in this file.
+ # export : -DLLVM_EXPORTS -> dllexport (dylib component libraries)
+ # static : -DLLVM_BUILD_STATIC -> (no annotation)
+ # import : neither -> dllimport
+ if(NOT LLVM_ENABLE_LLVM_EXPORT_ANNOTATIONS)
+ set(pch_mode "static")
+ elseif(kind STREQUAL "lib")
+ if(ARG_COMPONENT_LIB AND (LLVM_BUILD_LLVM_DYLIB OR BUILD_SHARED_LIBS))
+ set(pch_mode "export")
+ elseif(ARG_DISABLE_LLVM_LINK_LLVM_DYLIB)
+ set(pch_mode "static")
+ else()
+ set(pch_mode "import")
+ endif()
+ elseif(kind STREQUAL "exe")
+ if(ARG_DISABLE_LLVM_LINK_LLVM_DYLIB OR NOT LLVM_LINK_LLVM_DYLIB)
+ set(pch_mode "static")
+ else()
+ set(pch_mode "import")
+ endif()
+ else() # objlib: never receives LLVM_EXPORTS; LLVM_BUILD_STATIC iff disabled.
+ if(ARG_DISABLE_LLVM_LINK_LLVM_DYLIB)
+ set(pch_mode "static")
+ else()
+ set(pch_mode "import")
+ endif()
+ endif()
+
+ # A target may carry an additional ABI dimension that changes its predefines
+ # but is orthogonal to LLVM_ABI.
+ # For instance, clang uses CLANG_BUILD_STATIC / CLANG_EXPORTS, which clang
+ # always applies on Windows regardless of LLVM's export annotations.
+ if(LLVM_PCH_ABI_TAG)
+ set(pch_mode "${pch_mode}+${LLVM_PCH_ABI_TAG}")
+ 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
@@ -137,10 +179,41 @@ function(llvm_update_pch name)
# 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})
+ # Record the export mode this PCH was compiled in and the headers it used,
+ # so a dependant in a different mode can build its own PCH instead of
+ # reusing this one.
+ set_target_properties(${name} PROPERTIES
+ LLVM_PCH_MODE "${pch_mode}"
+ LLVM_PCH_HEADER "${ARG_PRECOMPILE_HEADERS}")
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})
+ get_target_property(reuse_mode ${pch_reuse} LLVM_PCH_MODE)
+ # Mode comparison is the single source of truth. When LLVM export
+ # annotations are off, every LLVM target's mode is uniformly "static" (see
+ # above), so this still reuses as before -- unless an ABI tag (e.g. clang's)
+ # makes the consumer's mode differ from the provider's.
+ if(pch_mode STREQUAL "${reuse_mode}")
+ # Same export mode: reuse the provider's PCH. Its _PchSym_ object lives in
+ # the provider, which this target links (the provider was found among our
+ # link dependencies), so it is present in our image.
+ message(DEBUG "Using PCH ${pch_reuse} for ${name} (prio ${pch_priority})")
+ target_precompile_headers(${name} REUSE_FROM ${pch_reuse})
+ else()
+ # Different export mode: the provider's PCH bakes the wrong dllexport/
+ # dllimport state (MSVC warns C4651, then miscompiles). It is also unsafe
+ # to reuse at link time -- with the dylib the provider is reached through
+ # LLVM.dll, so its _PchSym_ object is not in our image (LNK2011). Build
+ # our own PCH from the same header(s): compiled with this target's flags
+ # it matches, and its _PchSym_ lives in this target's own objects.
+ get_target_property(reuse_header ${pch_reuse} LLVM_PCH_HEADER)
+ if(reuse_header)
+ message(DEBUG "Building own ${pch_mode}-mode PCH for ${name}")
+ target_precompile_headers(${name} PRIVATE
+ $<$<COMPILE_LANGUAGE:CXX>:${reuse_header}>)
+ else()
+ message(DEBUG "Using NO PCH for ${name} (provider recorded no header)")
+ endif()
+ endif()
else()
message(DEBUG "Using NO PCH for ${name}")
endif()
@@ -648,7 +721,7 @@ function(llvm_add_library name)
${ALL_FILES}
)
llvm_update_compile_flags(${obj_name})
- llvm_update_pch(${obj_name})
+ llvm_update_pch(${obj_name} objlib)
if(CMAKE_GENERATOR STREQUAL "Xcode")
set(DUMMY_FILE ${CMAKE_CURRENT_BINARY_DIR}/Dummy.c)
file(WRITE ${DUMMY_FILE} "// This file intentionally empty\n")
@@ -695,7 +768,7 @@ function(llvm_add_library name)
endforeach()
endif()
- if(ARG_DISABLE_LLVM_LINK_LLVM_DYLIB)
+ if(ARG_DISABLE_LLVM_LINK_LLVM_DYLIB AND LLVM_ENABLE_LLVM_EXPORT_ANNOTATIONS)
target_compile_definitions(${obj_name} PRIVATE LLVM_BUILD_STATIC)
endif()
endif()
@@ -783,7 +856,7 @@ 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})
+ llvm_update_pch(${name} lib)
else()
get_target_property(lib_disable_pch ${obj_name} DISABLE_PRECOMPILE_HEADERS)
if(NOT ${lib_disable_pch})
@@ -865,7 +938,7 @@ function(llvm_add_library name)
if (LLVM_LINK_LLVM_DYLIB AND NOT ARG_DISABLE_LLVM_LINK_LLVM_DYLIB)
set(llvm_libs LLVM)
else()
- if(ARG_DISABLE_LLVM_LINK_LLVM_DYLIB)
+ if(ARG_DISABLE_LLVM_LINK_LLVM_DYLIB AND LLVM_ENABLE_LLVM_EXPORT_ANNOTATIONS)
target_compile_definitions(${name} PRIVATE LLVM_BUILD_STATIC)
endif()
llvm_map_components_to_libnames(llvm_libs
@@ -1103,7 +1176,7 @@ macro(generate_llvm_objects name)
${ALL_FILES}
)
llvm_update_compile_flags(${obj_name})
- llvm_update_pch(${obj_name})
+ llvm_update_pch(${obj_name} objlib)
set(ALL_FILES "$<TARGET_OBJECTS:${obj_name}>")
if(ARG_DEPENDS)
add_dependencies(${obj_name} ${ARG_DEPENDS})
@@ -1202,7 +1275,7 @@ 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})
+ llvm_update_pch(${name} exe)
elseif(NOT ARG_DISABLE_PCH_REUSE)
get_target_property(lib_disable_pch ${obj_name} DISABLE_PRECOMPILE_HEADERS)
if(NOT ${lib_disable_pch})
@@ -1276,7 +1349,8 @@ macro(add_llvm_executable name)
export_executable_symbols(${name})
endif()
- if(ARG_DISABLE_LLVM_LINK_LLVM_DYLIB OR NOT LLVM_LINK_LLVM_DYLIB)
+ if((ARG_DISABLE_LLVM_LINK_LLVM_DYLIB OR NOT LLVM_LINK_LLVM_DYLIB) AND
+ LLVM_ENABLE_LLVM_EXPORT_ANNOTATIONS)
target_compile_definitions(${name} PRIVATE LLVM_BUILD_STATIC)
endif()
diff --git a/llvm/tools/llvm-config/CMakeLists.txt b/llvm/tools/llvm-config/CMakeLists.txt
index fc285b2ba156a..34b56a96c5dc8 100644
--- a/llvm/tools/llvm-config/CMakeLists.txt
+++ b/llvm/tools/llvm-config/CMakeLists.txt
@@ -15,6 +15,10 @@ add_llvm_tool(llvm-config
# want to build an entire native libLLVM.so in addition to the cross one just
# for the native `llvm-config`!
DISABLE_LLVM_LINK_LLVM_DYLIB
+ # This utility defines CMAKE_CFG_INTDIR (below) directly on its compile line,
+ # which a shared LLVM PCH is not built with. It's a single small TU, so just
+ # opt out of PCH reuse.
+ DISABLE_PCH_REUSE
)
# Compute the substitution values for various items.
More information about the cfe-commits
mailing list