[Mlir-commits] [mlir] [MLIR][CMake] Add PCH for MLIRIR (PR #213412)
Maksim Levental
llvmlistbot at llvm.org
Sat Aug 1 01:21:04 PDT 2026
https://github.com/makslevental updated https://github.com/llvm/llvm-project/pull/213412
>From c72e559157248b271439ac1ebca6187634922328 Mon Sep 17 00:00:00 2001
From: makslevental <maksim.levental at gmail.com>
Date: Fri, 31 Jul 2026 22:56:14 -0700
Subject: [PATCH 1/2] [MLIR][CMake] Add PCH for MLIRIR
Add a precompiled header for MLIRIR, following the infrastructure added in
llvm#176420 and the per-component PCHs for llvm/IR (llvm#183303),
llvm/CodeGen (llvm#183346) and clang/AST (llvm#183358).
The header list was selected with the same methodology: compile all 1173
CUs under mlir/lib with -ftime-trace, rank mlir headers by the time spent
parsing them (including transitively included headers), then greedily pick
the header with the largest marginal coverage until the marginal gain
falls off. Candidates were restricted to headers already reachable from
MLIRIR's own sources, so the PCH does not invert the library layering.
Over the mlir/lib CUs, the selected set covers 1227s of 3674s total
frontend time (33%). On a Release+assertions build of mlir-opt
(Apple M-series, -j16), clean build wall time goes from 468.3s to 440.8s
(-5.9%); 715 of 1536 MLIR objects reuse the PCH.
mlir/lib/CMakeLists.txt gains an explicit add_subdirectory(IR) before the
other directories: CMake can only reuse a PCH from a target that is
already defined, so MLIRIR must exist before its dependants.
As with the earlier PCHs, the wider header visibility exposes a name
collision: mlir-lsp-server/LSPServer.cpp does `using namespace mlir` and
`using llvm::lsp::Location`, which becomes ambiguous once mlir::Location
is declared. Qualify the llvm::lsp::Location uses in that file.
---
mlir/include/mlir/IR/pch.h | 47 ++++++++++++++++++++
mlir/lib/CMakeLists.txt | 5 ++-
mlir/lib/IR/CMakeLists.txt | 3 ++
mlir/lib/Tools/mlir-lsp-server/LSPServer.cpp | 16 +++----
4 files changed, 62 insertions(+), 9 deletions(-)
create mode 100644 mlir/include/mlir/IR/pch.h
diff --git a/mlir/include/mlir/IR/pch.h b/mlir/include/mlir/IR/pch.h
new file mode 100644
index 0000000000000..69bb4ef798451
--- /dev/null
+++ b/mlir/include/mlir/IR/pch.h
@@ -0,0 +1,47 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// Precompiled header for MLIRIR.
+///
+//===----------------------------------------------------------------------===//
+
+#include "mlir/Bytecode/BytecodeImplementation.h"
+#include "mlir/Bytecode/BytecodeOpInterface.h"
+#include "mlir/IR/AffineExpr.h"
+#include "mlir/IR/AffineMap.h"
+#include "mlir/IR/AsmState.h"
+#include "mlir/IR/Attributes.h"
+#include "mlir/IR/Block.h"
+#include "mlir/IR/Builders.h"
+#include "mlir/IR/BuiltinAttributes.h"
+#include "mlir/IR/BuiltinOps.h"
+#include "mlir/IR/BuiltinTypes.h"
+#include "mlir/IR/Diagnostics.h"
+#include "mlir/IR/Dialect.h"
+#include "mlir/IR/Dominance.h"
+#include "mlir/IR/IRMapping.h"
+#include "mlir/IR/Location.h"
+#include "mlir/IR/MLIRContext.h"
+#include "mlir/IR/Matchers.h"
+#include "mlir/IR/OpDefinition.h"
+#include "mlir/IR/OpImplementation.h"
+#include "mlir/IR/Operation.h"
+#include "mlir/IR/OperationSupport.h"
+#include "mlir/IR/PatternMatch.h"
+#include "mlir/IR/Region.h"
+#include "mlir/IR/SymbolTable.h"
+#include "mlir/IR/TypeRange.h"
+#include "mlir/IR/TypeUtilities.h"
+#include "mlir/IR/Types.h"
+#include "mlir/IR/Value.h"
+#include "mlir/IR/ValueRange.h"
+#include "mlir/IR/Visitors.h"
+#include "mlir/Support/LLVM.h"
+#include "mlir/Support/TypeID.h"
+#include "mlir/Support/WalkResult.h"
+#include "llvm/Support/pch.h"
diff --git a/mlir/lib/CMakeLists.txt b/mlir/lib/CMakeLists.txt
index 576942b78f4a8..5b4f901bd80ad 100644
--- a/mlir/lib/CMakeLists.txt
+++ b/mlir/lib/CMakeLists.txt
@@ -1,6 +1,10 @@
# Enable errors for any global constructors.
add_flag_if_supported("-Werror=global-constructors" WERROR_GLOBAL_CONSTRUCTOR)
+# MLIRIR defines the PCH that most other MLIR libraries reuse. CMake can only
+# reuse a PCH from an already-defined target, so IR must come first.
+add_subdirectory(IR)
+
add_subdirectory(ABI)
add_subdirectory(Analysis)
add_subdirectory(AsmParser)
@@ -8,7 +12,6 @@ add_subdirectory(Bytecode)
add_subdirectory(Conversion)
add_subdirectory(Debug)
add_subdirectory(Dialect)
-add_subdirectory(IR)
add_subdirectory(Interfaces)
add_subdirectory(Parser)
add_subdirectory(Pass)
diff --git a/mlir/lib/IR/CMakeLists.txt b/mlir/lib/IR/CMakeLists.txt
index 6cadc3131ccd4..08a049980fcb9 100644
--- a/mlir/lib/IR/CMakeLists.txt
+++ b/mlir/lib/IR/CMakeLists.txt
@@ -50,6 +50,9 @@ add_mlir_library(MLIRIR
ADDITIONAL_HEADER_DIRS
${MLIR_MAIN_INCLUDE_DIR}/mlir/IR
+ PRECOMPILE_HEADERS
+ [["mlir/IR/pch.h"]]
+
DEPENDS
MLIRBuiltinAttributesIncGen
MLIRBuiltinAttributeInterfacesIncGen
diff --git a/mlir/lib/Tools/mlir-lsp-server/LSPServer.cpp b/mlir/lib/Tools/mlir-lsp-server/LSPServer.cpp
index 73ebe6a6b1f09..9df65dd61488e 100644
--- a/mlir/lib/Tools/mlir-lsp-server/LSPServer.cpp
+++ b/mlir/lib/Tools/mlir-lsp-server/LSPServer.cpp
@@ -32,7 +32,6 @@ using llvm::lsp::Hover;
using llvm::lsp::InitializedParams;
using llvm::lsp::InitializeParams;
using llvm::lsp::JSONTransport;
-using llvm::lsp::Location;
using llvm::lsp::Logger;
using llvm::lsp::MessageHandler;
using llvm::lsp::MLIRConvertBytecodeParams;
@@ -72,9 +71,9 @@ struct LSPServer {
// Definitions and References
void onGoToDefinition(const TextDocumentPositionParams ¶ms,
- Callback<std::vector<Location>> reply);
+ Callback<std::vector<llvm::lsp::Location>> reply);
void onReference(const ReferenceParams ¶ms,
- Callback<std::vector<Location>> reply);
+ Callback<std::vector<llvm::lsp::Location>> reply);
//===--------------------------------------------------------------------===//
// Hover
@@ -240,16 +239,17 @@ void LSPServer::onDocumentDidChange(const DidChangeTextDocumentParams ¶ms) {
// Definitions and References
//===----------------------------------------------------------------------===//
-void LSPServer::onGoToDefinition(const TextDocumentPositionParams ¶ms,
- Callback<std::vector<Location>> reply) {
- std::vector<Location> locations;
+void LSPServer::onGoToDefinition(
+ const TextDocumentPositionParams ¶ms,
+ Callback<std::vector<llvm::lsp::Location>> reply) {
+ std::vector<llvm::lsp::Location> locations;
server.getLocationsOf(params.textDocument.uri, params.position, locations);
reply(std::move(locations));
}
void LSPServer::onReference(const ReferenceParams ¶ms,
- Callback<std::vector<Location>> reply) {
- std::vector<Location> locations;
+ Callback<std::vector<llvm::lsp::Location>> reply) {
+ std::vector<llvm::lsp::Location> locations;
server.findReferencesOf(params.textDocument.uri, params.position, locations);
reply(std::move(locations));
}
>From 9947b7f554dd5b6f53c4c2020111082e35a38ea8 Mon Sep 17 00:00:00 2001
From: makslevental <maksim.levental at gmail.com>
Date: Sat, 1 Aug 2026 01:20:40 -0700
Subject: [PATCH 2/2] [MLIR][CMake] Extend MLIRIR PCH reuse to transitive
dependants
llvm_update_pch() only offers a PCH to targets that name the defining
library as a *direct* dependency, because across LLVM subprojects a
transitively reused PCH drags in unrelated headers and causes name
collisions. Within MLIR that concern is much weaker: mlir/IR/pch.h holds
MLIR core headers that essentially every MLIR library includes anyway.
Add mlir_reuse_ir_pch(), which offers MLIRIR's PCH to any target that
actually reaches MLIRIR through its link graph. The reachability check
matters: a PCH containing MLIR IR headers emits out-of-line symbols that
only libMLIRIR provides, so handing it to a Support-only target such as
tblgen-lsp-server breaks the link.
The helper is called from add_mlir_library() and add_mlir_tool(), and
again from mlir_target_link_libraries(), where most tests, unittests and
libMLIR.so-avoiding libraries actually attach their MLIR dependencies. It
is idempotent and skips targets that define their own PCH, that opt out
with DISABLE_PCH_REUSE, that override RTTI/EH, or that contain C/ObjC
sources. MLIRSupport is excluded because MLIRIR links it, and PCH reuse
creates a real target dependency that would form a cycle.
add_mlir_library() now parses DISABLE_PCH_REUSE itself, so the opt-out is
visible here; previously it was consumed by llvm_add_library and left no
queryable property, which silently defeated the C API opt-out from
llvm#182862.
mlir/lib/CMakeLists.txt processes Interfaces, Pass and Rewrite right after
IR, so that the libraries carrying the dependency edge to MLIRIR exist
before their dependants are configured.
PDLL opts out: mlir::pdll::ast::Type/TupleType/InFlightDiagnostic and
mlir::pdll::ods::Dialect/Operation collide with the mlir:: names in the
PCH. Those libraries only link MLIRSupport, so the PCH is of no use there.
Together this takes PCH reuse from 715 to 1122 of 1536 MLIR objects.
check-mlir passes with 0 unexpected failures.
---
mlir/cmake/modules/AddMLIR.cmake | 137 ++++++++++++++++++++++++-
mlir/lib/CMakeLists.txt | 11 +-
mlir/lib/Tools/PDLL/AST/CMakeLists.txt | 5 +
mlir/lib/Tools/PDLL/ODS/CMakeLists.txt | 5 +
mlir/unittests/CMakeLists.txt | 1 +
5 files changed, 153 insertions(+), 6 deletions(-)
diff --git a/mlir/cmake/modules/AddMLIR.cmake b/mlir/cmake/modules/AddMLIR.cmake
index d3f534a02f9de..69d4f8cba47c1 100644
--- a/mlir/cmake/modules/AddMLIR.cmake
+++ b/mlir/cmake/modules/AddMLIR.cmake
@@ -305,6 +305,121 @@ function(add_mlir_example_library name)
endif()
endfunction()
+# Determine whether a target reaches MLIRIR through its link libraries.
+function(_mlir_links_ir name out)
+ set(seen "")
+ set(worklist ${name})
+ while(worklist)
+ list(POP_FRONT worklist current)
+ if(current IN_LIST seen OR NOT TARGET ${current})
+ continue()
+ endif()
+ list(APPEND seen ${current})
+ if(current STREQUAL "MLIRIR" OR current STREQUAL "obj.MLIRIR")
+ set(${out} TRUE PARENT_SCOPE)
+ return()
+ endif()
+ get_target_property(type ${current} TYPE)
+ set(libs "")
+ if(NOT type STREQUAL "INTERFACE_LIBRARY")
+ get_target_property(link_libs ${current} LINK_LIBRARIES)
+ if(link_libs)
+ list(APPEND libs ${link_libs})
+ endif()
+ endif()
+ get_target_property(iface_libs ${current} INTERFACE_LINK_LIBRARIES)
+ if(iface_libs)
+ list(APPEND libs ${iface_libs})
+ endif()
+ foreach(lib ${libs})
+ # Skip generator expressions; they can't be resolved at configure time.
+ if(NOT lib MATCHES "\\$<")
+ list(APPEND worklist ${lib})
+ endif()
+ endforeach()
+ endwhile()
+ set(${out} FALSE PARENT_SCOPE)
+endfunction()
+
+# Extend PCH reuse of MLIRIR's PCH to a target that reaches MLIRIR only
+# transitively. llvm_update_pch() intentionally limits reuse to direct
+# dependencies, because across LLVM subprojects a transitively reused PCH
+# pulls in unrelated headers and causes name collisions. Within MLIR that
+# concern doesn't apply: the PCH holds MLIR core headers that essentially
+# every MLIR library includes already.
+#
+# Such a target typically already reuses the LLVMSupport PCH, picked up from
+# a direct dependency. Switching it to MLIRIR's is a strict improvement:
+# mlir/IR/pch.h includes llvm/Support/pch.h, so nothing is lost.
+#
+# Does nothing if the target defines its own PCH, if PCH is disabled for it,
+# if it does not link MLIRIR, or if MLIRIR itself has no usable PCH.
+function(mlir_reuse_ir_pch name)
+ if(CMAKE_DISABLE_PRECOMPILE_HEADERS OR NOT TARGET MLIRIR OR NOT TARGET ${name})
+ return()
+ endif()
+ # Reuse creates a real target dependency, so anything MLIRIR itself builds
+ # on would form a cycle. MLIRSupport is the only such MLIR library.
+ if(name STREQUAL "MLIRIR" OR name STREQUAL "obj.MLIRIR"
+ OR name STREQUAL "MLIRSupport" OR name STREQUAL "obj.MLIRSupport")
+ return()
+ endif()
+
+ # An object library owns the compilation, so that is what carries the PCH
+ # and what needs to reuse one.
+ set(source MLIRIR)
+ if(TARGET obj.MLIRIR)
+ set(source obj.MLIRIR)
+ endif()
+ get_target_property(mlirir_pch ${source} PRECOMPILE_HEADERS)
+ if(NOT mlirir_pch)
+ return()
+ endif()
+
+ set(target ${name})
+ if(TARGET obj.${name})
+ set(target obj.${name})
+ endif()
+
+ # Don't override a PCH the target defines itself, and respect an opt-out.
+ get_target_property(existing ${target} PRECOMPILE_HEADERS)
+ get_target_property(disabled ${target} DISABLE_PRECOMPILE_HEADERS)
+ if(existing OR disabled)
+ return()
+ endif()
+
+ # Only replace a reused PCH, never a self-defined one. Anything MLIR reuses
+ # at this point comes from LLVMSupport, which MLIRIR's PCH subsumes.
+ get_target_property(reused ${target} PRECOMPILE_HEADERS_REUSE_FROM)
+ if(reused AND NOT reused STREQUAL "LLVMSupport")
+ return()
+ endif()
+
+ # The PCH pulls in MLIR IR headers, which emit out-of-line symbols that only
+ # libMLIRIR provides. A target that doesn't link MLIRIR would fail to link.
+ _mlir_links_ir(${name} links_ir)
+ if(NOT links_ir)
+ return()
+ endif()
+
+ # The PCH is compiled without RTTI/exceptions and as C++; a target that
+ # overrides any of that cannot reuse it.
+ if(LLVM_REQUIRES_RTTI OR LLVM_REQUIRES_EH)
+ return()
+ endif()
+ get_property(srcs TARGET ${target} PROPERTY SOURCES)
+ foreach(src ${srcs})
+ get_filename_component(extension ${src} EXT)
+ if(extension STREQUAL ".c" OR extension STREQUAL ".m" OR extension STREQUAL ".mm")
+ return()
+ endif()
+ endforeach()
+
+ message(DEBUG "Reusing MLIRIR PCH for ${target}")
+ set_target_properties(${target} PROPERTIES PRECOMPILE_HEADERS_REUSE_FROM "")
+ target_precompile_headers(${target} REUSE_FROM ${source})
+endfunction()
+
# Declare an mlir library which can be compiled in libMLIR.so
# In addition to everything that llvm_add_library accepts, this
# also has the following option:
@@ -327,7 +442,7 @@ endfunction()
# Don't link against LLVMSupport.
function(add_mlir_library name)
cmake_parse_arguments(ARG
- "SHARED;INSTALL_WITH_TOOLCHAIN;EXCLUDE_FROM_LIBMLIR;DISABLE_INSTALL;ENABLE_AGGREGATION;OBJECT;STANDALONE"
+ "SHARED;INSTALL_WITH_TOOLCHAIN;EXCLUDE_FROM_LIBMLIR;DISABLE_INSTALL;ENABLE_AGGREGATION;OBJECT;STANDALONE;DISABLE_PCH_REUSE"
""
"ADDITIONAL_HEADERS;DEPENDS;LINK_COMPONENTS;LINK_LIBS"
${ARGN})
@@ -387,7 +502,18 @@ function(add_mlir_library name)
_check_llvm_components_usage(${name} ${ARG_LINK_LIBS})
list(APPEND ARG_DEPENDS mlir-generic-headers)
- llvm_add_library(${name} ${LIBTYPE} ${ARG_UNPARSED_ARGUMENTS} ${srcs} DEPENDS ${ARG_DEPENDS} LINK_COMPONENTS ${ARG_LINK_COMPONENTS} LINK_LIBS ${ARG_LINK_LIBS})
+ if(ARG_DISABLE_PCH_REUSE)
+ set(disable_pch_reuse DISABLE_PCH_REUSE)
+ endif()
+ llvm_add_library(${name} ${LIBTYPE} ${ARG_UNPARSED_ARGUMENTS} ${srcs} ${disable_pch_reuse} DEPENDS ${ARG_DEPENDS} LINK_COMPONENTS ${ARG_LINK_COMPONENTS} LINK_LIBS ${ARG_LINK_LIBS})
+
+ # llvm_update_pch() only considers direct dependencies when looking for a PCH
+ # to reuse, so libraries that reach MLIRIR only transitively don't pick up its
+ # PCH. Within MLIR that's needlessly restrictive: the PCH holds MLIR core
+ # headers, which those libraries include anyway. Extend reuse to them.
+ if(NOT ARG_DISABLE_PCH_REUSE)
+ mlir_reuse_ir_pch(${name})
+ endif()
if(TARGET ${name})
target_link_libraries(${name} INTERFACE ${LLVM_COMMON_LIBS})
@@ -446,6 +572,7 @@ endfunction(add_mlir_library)
macro(add_mlir_tool name)
llvm_add_tool(MLIR ${ARGV})
+ mlir_reuse_ir_pch(${name})
endmacro()
# Sets a variable with a transformed list of link libraries such individual
@@ -755,4 +882,10 @@ function(mlir_target_link_libraries target type)
else()
target_link_libraries(${target} ${type} ${ARGN})
endif()
+
+ # Many targets (tests, unittests, and libraries that must avoid a hard
+ # dependency on libMLIR.so) get their MLIR dependencies here rather than
+ # through add_mlir_library's LINK_LIBS. Re-check PCH reuse now that the link
+ # graph is complete: the earlier attempt could not see these libraries yet.
+ mlir_reuse_ir_pch(${target})
endfunction()
diff --git a/mlir/lib/CMakeLists.txt b/mlir/lib/CMakeLists.txt
index 5b4f901bd80ad..0b699d076eb40 100644
--- a/mlir/lib/CMakeLists.txt
+++ b/mlir/lib/CMakeLists.txt
@@ -2,8 +2,14 @@
add_flag_if_supported("-Werror=global-constructors" WERROR_GLOBAL_CONSTRUCTOR)
# MLIRIR defines the PCH that most other MLIR libraries reuse. CMake can only
-# reuse a PCH from an already-defined target, so IR must come first.
+# reuse a PCH from an already-defined target, and reuse is only extended to
+# libraries that actually reach MLIRIR through their link graph, so both MLIRIR
+# and the low-level libraries that carry the dependency edge to it have to be
+# defined before their dependants.
add_subdirectory(IR)
+add_subdirectory(Interfaces)
+add_subdirectory(Pass)
+add_subdirectory(Rewrite)
add_subdirectory(ABI)
add_subdirectory(Analysis)
@@ -12,13 +18,10 @@ add_subdirectory(Bytecode)
add_subdirectory(Conversion)
add_subdirectory(Debug)
add_subdirectory(Dialect)
-add_subdirectory(Interfaces)
add_subdirectory(Parser)
-add_subdirectory(Pass)
add_subdirectory(Query)
add_subdirectory(Reducer)
add_subdirectory(Remark)
-add_subdirectory(Rewrite)
add_subdirectory(Support)
add_subdirectory(TableGen)
add_subdirectory(Target)
diff --git a/mlir/lib/Tools/PDLL/AST/CMakeLists.txt b/mlir/lib/Tools/PDLL/AST/CMakeLists.txt
index 5e67ee02b9c7b..7e47b5e5ed694 100644
--- a/mlir/lib/Tools/PDLL/AST/CMakeLists.txt
+++ b/mlir/lib/Tools/PDLL/AST/CMakeLists.txt
@@ -5,6 +5,11 @@ add_mlir_library(MLIRPDLLAST
Nodes.cpp
Types.cpp
+ # The PDLL AST defines its own Type, TupleType and InFlightDiagnostic in
+ # mlir::pdll::ast, which collide with the mlir:: ones in the MLIRIR PCH.
+ # This library only depends on MLIRSupport, so it has no use for that PCH.
+ DISABLE_PCH_REUSE
+
LINK_LIBS PUBLIC
MLIRPDLLODS
MLIRSupport
diff --git a/mlir/lib/Tools/PDLL/ODS/CMakeLists.txt b/mlir/lib/Tools/PDLL/ODS/CMakeLists.txt
index 6a9abc903418a..4cfa6096f48eb 100644
--- a/mlir/lib/Tools/PDLL/ODS/CMakeLists.txt
+++ b/mlir/lib/Tools/PDLL/ODS/CMakeLists.txt
@@ -4,6 +4,11 @@ add_mlir_library(MLIRPDLLODS
Dialect.cpp
Operation.cpp
+ # The PDLL ODS model defines its own Dialect and Operation in mlir::pdll::ods,
+ # which collide with the mlir:: ones in the MLIRIR PCH. This library only
+ # depends on MLIRSupport, so it has no use for that PCH.
+ DISABLE_PCH_REUSE
+
LINK_LIBS PUBLIC
MLIRSupport
)
diff --git a/mlir/unittests/CMakeLists.txt b/mlir/unittests/CMakeLists.txt
index 654ec44d90b04..4382b71f901eb 100644
--- a/mlir/unittests/CMakeLists.txt
+++ b/mlir/unittests/CMakeLists.txt
@@ -3,6 +3,7 @@ add_definitions(-DGTEST_NO_LLVM_SUPPORT=0)
function(add_mlir_unittest test_dirname)
add_unittest(MLIRUnitTests ${test_dirname} ${ARGN})
+ mlir_reuse_ir_pch(${test_dirname})
endfunction()
add_subdirectory(ABI)
More information about the Mlir-commits
mailing list