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
