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 <[email protected]>
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.

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

Reply via email to