From 8c8f03422e3197a3414a8ed1413fcb9576df0f4b Mon Sep 17 00:00:00 2001
From: Tobias Hieta <tobias@plex.tv>
Date: Fri, 28 Aug 2020 09:16:04 +0200
Subject: PCH: Template instantiation support

Adds PCH_INSTANTIATE_TEMPLATES target property for enabling template
instantiation in precompiled headers.
Enabled by default. Currently only supported for Clang 11 and newer.

Implements #21133.
---
 Help/manual/cmake-properties.7.rst                      |  1 +
 Help/manual/cmake-variables.7.rst                       |  1 +
 Help/prop_tgt/PCH_INSTANTIATE_TEMPLATES.rst             | 13 +++++++++++++
 Help/release/dev/PCH_INSTANTIATE_TEMPLATES.rst          |  7 +++++++
 Help/variable/CMAKE_PCH_INSTANTIATE_TEMPLATES.rst       |  7 +++++++
 Modules/Compiler/Clang.cmake                            |  3 +++
 Source/cmGeneratorTarget.cxx                            | 10 ++++++++++
 Source/cmTarget.cxx                                     |  1 +
 Tests/RunCMake/CMakeLists.txt                           |  4 +++-
 .../PchInstantiateTemplates-check.cmake                 | 17 +++++++++++++++++
 .../PrecompileHeaders/PchInstantiateTemplates.cmake     | 16 ++++++++++++++++
 Tests/RunCMake/PrecompileHeaders/RunCMakeTest.cmake     |  5 +++++
 12 files changed, 84 insertions(+), 1 deletion(-)
 create mode 100644 Help/prop_tgt/PCH_INSTANTIATE_TEMPLATES.rst
 create mode 100644 Help/release/dev/PCH_INSTANTIATE_TEMPLATES.rst
 create mode 100644 Help/variable/CMAKE_PCH_INSTANTIATE_TEMPLATES.rst
 create mode 100644 Tests/RunCMake/PrecompileHeaders/PchInstantiateTemplates-check.cmake
 create mode 100644 Tests/RunCMake/PrecompileHeaders/PchInstantiateTemplates.cmake

diff --git a/Help/manual/cmake-properties.7.rst b/Help/manual/cmake-properties.7.rst
index afdf78c..b2e3995 100644
--- a/Help/manual/cmake-properties.7.rst
+++ b/Help/manual/cmake-properties.7.rst
@@ -313,6 +313,7 @@ Properties on Targets
    /prop_tgt/OUTPUT_NAME_CONFIG
    /prop_tgt/OUTPUT_NAME
    /prop_tgt/PCH_WARN_INVALID
+   /prop_tgt/PCH_INSTANTIATE_TEMPLATES
    /prop_tgt/PDB_NAME_CONFIG
    /prop_tgt/PDB_NAME
    /prop_tgt/PDB_OUTPUT_DIRECTORY_CONFIG
diff --git a/Help/manual/cmake-variables.7.rst b/Help/manual/cmake-variables.7.rst
index 53cdd0b..baf4a55 100644
--- a/Help/manual/cmake-variables.7.rst
+++ b/Help/manual/cmake-variables.7.rst
@@ -443,6 +443,7 @@ Variables that Control the Build
    /variable/CMAKE_OSX_DEPLOYMENT_TARGET
    /variable/CMAKE_OSX_SYSROOT
    /variable/CMAKE_PCH_WARN_INVALID
+   /variable/CMAKE_PCH_INSTANTIATE_TEMPLATES
    /variable/CMAKE_PDB_OUTPUT_DIRECTORY
    /variable/CMAKE_PDB_OUTPUT_DIRECTORY_CONFIG
    /variable/CMAKE_POSITION_INDEPENDENT_CODE
diff --git a/Help/prop_tgt/PCH_INSTANTIATE_TEMPLATES.rst b/Help/prop_tgt/PCH_INSTANTIATE_TEMPLATES.rst
new file mode 100644
index 0000000..7c1af2a
--- /dev/null
+++ b/Help/prop_tgt/PCH_INSTANTIATE_TEMPLATES.rst
@@ -0,0 +1,13 @@
+PCH_INSTANTIATE_TEMPLATES
+-------------------------
+
+.. versionadded:: 3.19
+
+When this property is set to true, the precompiled header compiler options
+will contain a flag to instantiate templates during the generation of the PCH
+if supported. This can significantly improve compile times. Supported in Clang
+since version 11.
+
+This property is initialized by the value of the
+:variable:`CMAKE_PCH_INSTANTIATE_TEMPLATES` variable if it is set when a target
+is created.  If that variable is not set, the property defaults to ``ON``.
diff --git a/Help/release/dev/PCH_INSTANTIATE_TEMPLATES.rst b/Help/release/dev/PCH_INSTANTIATE_TEMPLATES.rst
new file mode 100644
index 0000000..0334204
--- /dev/null
+++ b/Help/release/dev/PCH_INSTANTIATE_TEMPLATES.rst
@@ -0,0 +1,7 @@
+PCH_INSTANTIATE_TEMPLATES
+-------------------------
+
+* The :prop_tgt:`PCH_INSTANTIATE_TEMPLATES` target property was added to enable
+  template instantiation in the precompiled header. This is enabled by default
+  and offers a roughly 20% compile time improvement. Currently only supported
+  by Clang 11.
diff --git a/Help/variable/CMAKE_PCH_INSTANTIATE_TEMPLATES.rst b/Help/variable/CMAKE_PCH_INSTANTIATE_TEMPLATES.rst
new file mode 100644
index 0000000..9867f17
--- /dev/null
+++ b/Help/variable/CMAKE_PCH_INSTANTIATE_TEMPLATES.rst
@@ -0,0 +1,7 @@
+CMAKE_PCH_INSTANTIATE_TEMPLATES
+-------------------------------
+
+.. versionadded:: 3.19
+
+This variable is used to initialize the :prop_tgt:`PCH_INSTANTIATE_TEMPLATES`
+property of targets when they are created.
diff --git a/Modules/Compiler/Clang.cmake b/Modules/Compiler/Clang.cmake
index 52d5350..6f0e9b3 100644
--- a/Modules/Compiler/Clang.cmake
+++ b/Modules/Compiler/Clang.cmake
@@ -101,6 +101,9 @@ else()
     if (NOT CMAKE_GENERATOR MATCHES "Xcode")
       set(CMAKE_PCH_PROLOGUE "#pragma clang system_header")
     endif()
+    if(CMAKE_${lang}_COMPILER_VERSION VERSION_GREATER_EQUAL 11.0.0 AND NOT __is_apple_clang)
+      set(CMAKE_${lang}_COMPILE_OPTIONS_INSTANTIATE_TEMPLATES_PCH -fpch-instantiate-templates)
+    endif()
     set(CMAKE_${lang}_COMPILE_OPTIONS_USE_PCH -Xclang -include-pch -Xclang <PCH_FILE> -Xclang -include -Xclang <PCH_HEADER>)
     set(CMAKE_${lang}_COMPILE_OPTIONS_CREATE_PCH -Xclang -emit-pch -Xclang -include -Xclang <PCH_HEADER>)
   endmacro()
diff --git a/Source/cmGeneratorTarget.cxx b/Source/cmGeneratorTarget.cxx
index 7c526a0..de981c1 100644
--- a/Source/cmGeneratorTarget.cxx
+++ b/Source/cmGeneratorTarget.cxx
@@ -4078,6 +4078,16 @@ std::string cmGeneratorTarget::GetPchCreateCompileOptions(
         cmStrCat("CMAKE_", language, "_COMPILE_OPTIONS_INVALID_PCH"));
     }
 
+    if (this->GetPropertyAsBool("PCH_INSTANTIATE_TEMPLATES")) {
+      std::string varName = cmStrCat(
+        "CMAKE_", language, "_COMPILE_OPTIONS_INSTANTIATE_TEMPLATES_PCH");
+      std::string instantiateOption =
+        this->Makefile->GetSafeDefinition(varName);
+      if (!instantiateOption.empty()) {
+        createOptionList = cmStrCat(createOptionList, ";", instantiateOption);
+      }
+    }
+
     const std::string createOptVar =
       cmStrCat("CMAKE_", language, "_COMPILE_OPTIONS_CREATE_PCH");
 
diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx
index bea9001..6aa23c2 100644
--- a/Source/cmTarget.cxx
+++ b/Source/cmTarget.cxx
@@ -377,6 +377,7 @@ cmTarget::cmTarget(std::string const& name, cmStateEnums::TargetType type,
     initPropValue("UNITY_BUILD_BATCH_SIZE", "8");
     initPropValue("UNITY_BUILD_MODE", "BATCH");
     initPropValue("PCH_WARN_INVALID", "ON");
+    initPropValue("PCH_INSTANTIATE_TEMPLATES", "ON");
 
 #ifdef __APPLE__
     if (this->GetGlobalGenerator()->IsXcode()) {
diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt
index 65e8b0b..88d0004 100644
--- a/Tests/RunCMake/CMakeLists.txt
+++ b/Tests/RunCMake/CMakeLists.txt
@@ -731,7 +731,9 @@ endif()
 
 add_RunCMake_test("CTestCommandExpandLists")
 
-add_RunCMake_test(PrecompileHeaders -DCMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID})
+add_RunCMake_test(PrecompileHeaders -DCMAKE_C_COMPILER_ID=${CMAKE_C_COMPILER_ID}
+  -DCMAKE_C_COMPILER_VERSION=${CMAKE_C_COMPILER_VERSION})
+
 add_RunCMake_test("UnityBuild")
 
 if(WIN32)
diff --git a/Tests/RunCMake/PrecompileHeaders/PchInstantiateTemplates-check.cmake b/Tests/RunCMake/PrecompileHeaders/PchInstantiateTemplates-check.cmake
new file mode 100644
index 0000000..648d387
--- /dev/null
+++ b/Tests/RunCMake/PrecompileHeaders/PchInstantiateTemplates-check.cmake
@@ -0,0 +1,17 @@
+file(STRINGS ${RunCMake_TEST_BINARY_DIR}/compile_commands.json empty_dir_commands
+     REGEX "command.*-fpch-instantiate-templates.*empty.dir/cmake_pch.h")
+file(STRINGS ${RunCMake_TEST_BINARY_DIR}/compile_commands.json foo_dir_commands
+     REGEX "command.*-fpch-instantiate-templates.*foo.dir/cmake_pch.h")
+
+list(LENGTH empty_dir_commands empty_dir_commands_size)
+list(LENGTH foo_dir_commands foo_dir_commands_size)
+
+if (empty_dir_commands_size EQUAL 0)
+  set(RunCMake_TEST_FAILED "empty target should have -fpch-instantiate-templates compile option present")
+  return()
+endif()
+
+if (foo_dir_commands_size GREATER 0)
+  set(RunCMake_TEST_FAILED "foo target should not have -fpch-instantiate-templates compile option present")
+  return()
+endif()
diff --git a/Tests/RunCMake/PrecompileHeaders/PchInstantiateTemplates.cmake b/Tests/RunCMake/PrecompileHeaders/PchInstantiateTemplates.cmake
new file mode 100644
index 0000000..3aebbe0
--- /dev/null
+++ b/Tests/RunCMake/PrecompileHeaders/PchInstantiateTemplates.cmake
@@ -0,0 +1,16 @@
+enable_language(C)
+
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+add_library(empty empty.c)
+target_precompile_headers(empty PUBLIC
+  <stdio.h>
+  <string.h>
+)
+
+add_library(foo foo.c)
+target_precompile_headers(foo PUBLIC
+  <stdio.h>
+  <string.h>
+)
+set_target_properties(foo PROPERTIES PCH_INSTANTIATE_TEMPLATES OFF)
diff --git a/Tests/RunCMake/PrecompileHeaders/RunCMakeTest.cmake b/Tests/RunCMake/PrecompileHeaders/RunCMakeTest.cmake
index 381b800..74670ba 100644
--- a/Tests/RunCMake/PrecompileHeaders/RunCMakeTest.cmake
+++ b/Tests/RunCMake/PrecompileHeaders/RunCMakeTest.cmake
@@ -20,4 +20,9 @@ run_test(PchReuseFromSubdir)
 run_cmake(PchMultilanguage)
 if(RunCMake_GENERATOR MATCHES "Make|Ninja")
   run_cmake(PchWarnInvalid)
+
+  if(CMAKE_C_COMPILER_ID STREQUAL "Clang" AND
+     CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 11.0.0)
+    run_cmake(PchInstantiateTemplates)
+  endif()
 endif()
-- 
cgit v0.12