c_parse_final_cleanups writes the PCH before calling
instantiate_pending_templates, so every translation unit using the PCH
redoes the template instantiations that the header's inline functions
need, even if it uses nothing from the header. For a large header this
dominates the cost of small translation units.
This patch adds -fpch-instantiate-templates to do these instantiations
before the PCH is written (mirroring the option available in Clang).
This moves their point of instantiation to the end of the header, which
only matters for programs that are ill-formed, no diagnostic required
([temp.point]). Headers that are not self-contained can now fail to
build though, e.g. if ADL finds a function that is only declared after
the header. Diagnostics from these instantiations are issued when the
PCH is created, and a specialization named in an explicit instantiation
declaration after the header can now be inlined.
With the same header as the previous patch, on top of it:
empty TU 92 functions
without option 356 MB 7.8 s 606 MB 25.9 s
with option 149 MB 1.9 s 395 MB 19.5 s
Building the PCH takes 30 s and 1761 MB instead of 21 s and 1363 MB,
and the PCH grows from 500 MB to 653 MB.
Assisted-by: Claude Opus 5.5
gcc/c-family/ChangeLog:
* c.opt (fpch-instantiate-templates): New option.
* c.opt.urls: Regenerate.
gcc/cp/ChangeLog:
* decl2.cc (c_parse_final_cleanups): Call
instantiate_pending_templates before writing a PCH if
-fpch-instantiate-templates.
gcc/ChangeLog:
* doc/invoke.texi (C++ Dialect Options): Document
-fpch-instantiate-templates.
(Precompiled Headers): Refer to it.
gcc/testsuite/ChangeLog:
* g++.dg/pch/pch-instantiate-1.C: New test.
* g++.dg/pch/pch-instantiate-1.Hs: New test.
* g++.dg/pch/pch-instantiate-2.C: New test.
* g++.dg/pch/pch-instantiate-2.Hs: New test.
* g++.dg/pch/pch-instantiate-3.C: New test.
* g++.dg/pch/pch-instantiate-3.Hs: New test.
* g++.dg/pch/pch-instantiate-4.C: New test.
* g++.dg/pch/pch-instantiate-4.Hs: New test.
* g++.dg/pch/pch-instantiate-5.C: New test.
* g++.dg/pch/pch-instantiate-5.Hs: New test.
* g++.dg/template/pch-instantiate-1.C: New test.
* g++.dg/template/pch-instantiate-2.C: New test.
* g++.dg/template/pch-instantiate-3.C: New test.
* g++.dg/template/pch-instantiate-4.C: New test.
Signed-off-by: Chris Burr <[email protected]>
---
gcc/c-family/c.opt | 4 ++
gcc/c-family/c.opt.urls | 3 +
gcc/cp/decl2.cc | 5 ++
gcc/doc/invoke.texi | 31 +++++++++-
gcc/testsuite/g++.dg/pch/pch-instantiate-1.C | 7 +++
gcc/testsuite/g++.dg/pch/pch-instantiate-1.Hs | 56 +++++++++++++++++++
gcc/testsuite/g++.dg/pch/pch-instantiate-2.C | 1 +
gcc/testsuite/g++.dg/pch/pch-instantiate-2.Hs | 15 +++++
gcc/testsuite/g++.dg/pch/pch-instantiate-3.C | 11 ++++
gcc/testsuite/g++.dg/pch/pch-instantiate-3.Hs | 14 +++++
gcc/testsuite/g++.dg/pch/pch-instantiate-4.C | 11 ++++
gcc/testsuite/g++.dg/pch/pch-instantiate-4.Hs | 34 +++++++++++
gcc/testsuite/g++.dg/pch/pch-instantiate-5.C | 20 +++++++
gcc/testsuite/g++.dg/pch/pch-instantiate-5.Hs | 22 ++++++++
.../g++.dg/template/pch-instantiate-1.C | 22 ++++++++
.../g++.dg/template/pch-instantiate-2.C | 21 +++++++
.../g++.dg/template/pch-instantiate-3.C | 19 +++++++
.../g++.dg/template/pch-instantiate-4.C | 24 ++++++++
18 files changed, 319 insertions(+), 1 deletion(-)
create mode 100644 gcc/testsuite/g++.dg/pch/pch-instantiate-1.C
create mode 100644 gcc/testsuite/g++.dg/pch/pch-instantiate-1.Hs
create mode 100644 gcc/testsuite/g++.dg/pch/pch-instantiate-2.C
create mode 100644 gcc/testsuite/g++.dg/pch/pch-instantiate-2.Hs
create mode 100644 gcc/testsuite/g++.dg/pch/pch-instantiate-3.C
create mode 100644 gcc/testsuite/g++.dg/pch/pch-instantiate-3.Hs
create mode 100644 gcc/testsuite/g++.dg/pch/pch-instantiate-4.C
create mode 100644 gcc/testsuite/g++.dg/pch/pch-instantiate-4.Hs
create mode 100644 gcc/testsuite/g++.dg/pch/pch-instantiate-5.C
create mode 100644 gcc/testsuite/g++.dg/pch/pch-instantiate-5.Hs
create mode 100644 gcc/testsuite/g++.dg/template/pch-instantiate-1.C
create mode 100644 gcc/testsuite/g++.dg/template/pch-instantiate-2.C
create mode 100644 gcc/testsuite/g++.dg/template/pch-instantiate-3.C
create mode 100644 gcc/testsuite/g++.dg/template/pch-instantiate-4.C
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index d5b973baa85..509de105526 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -2333,6 +2333,10 @@ Does nothing. Preserved for backward compatibility.
fpch-deps
C ObjC C++ ObjC++
+fpch-instantiate-templates
+C++ ObjC++ Var(flag_pch_instantiate_templates)
+Instantiate the templates used by a header when creating a precompiled header.
+
fpch-preprocess
C ObjC C++ ObjC++
Look for and use PCH files even when preprocessing.
diff --git a/gcc/c-family/c.opt.urls b/gcc/c-family/c.opt.urls
index 6efeea9b42f..610b3f84202 100644
--- a/gcc/c-family/c.opt.urls
+++ b/gcc/c-family/c.opt.urls
@@ -1492,6 +1492,9 @@
UrlSuffix(gcc/C_002b_002b-Dialect-Options.html#index-fno-optional-diags)
fpch-deps
UrlSuffix(gcc/Preprocessor-Options.html#index-fno-pch-deps)
+fpch-instantiate-templates
+UrlSuffix(gcc/C_002b_002b-Dialect-Options.html#index-fno-pch-instantiate-templates)
+
fpch-preprocess
UrlSuffix(gcc/Preprocessor-Options.html#index-fno-pch-preprocess)
diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc
index 563fae5db0d..7819b8b6afb 100644
--- a/gcc/cp/decl2.cc
+++ b/gcc/cp/decl2.cc
@@ -5821,6 +5821,11 @@ c_parse_final_cleanups (void)
In that case we do not want to do anything else. */
if (pch_file)
{
+ /* Do the instantiations that the header needs now, so that the
+ translation units using the PCH do not each have to do them. */
+ if (flag_pch_instantiate_templates)
+ instantiate_pending_templates (0);
+
/* Mangle all symbols at PCH creation time. */
symtab_node *node;
FOR_EACH_SYMBOL (node)
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 66d80f17e74..86ab2fd8381 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -249,7 +249,7 @@ in the following sections.
-fnew-inheriting-ctors
-fnew-ttp-matching
-fno-nonansi-builtins -fnothrow-opt -fno-operator-names
--fno-optional-diags
+-fno-optional-diags -fpch-instantiate-templates
-fno-pretty-templates -frange-for-ext-temps -freflection
-fno-rtti -fsized-deallocation
-fstrict-enums -fstrong-eval-order@r{[}=@var{kind}@r{]}
@@ -3718,6 +3718,29 @@ Disable diagnostics that the standard says a compiler
does not need to
issue. Currently, the only such diagnostic issued by G++ is the one for
a name having multiple meanings within a class.
+@opindex fpch-instantiate-templates
+@opindex fno-pch-instantiate-templates
+@item -fpch-instantiate-templates
+When creating a precompiled header, instantiate the templates that the
+header itself requires, such as those used by its inline functions,
+before writing the precompiled header (@pxref{Precompiled Headers}).
+Otherwise these instantiations are done in every translation unit that
+uses the precompiled header. With large headers, this can save
+considerable time and memory in those translation units, at the cost of
+a larger precompiled header.
+
+The instantiations are done at the end of the header, so declarations
+that follow the header do not affect them. This only makes a
+difference for headers that are not self-contained, for example if
+argument-dependent lookup should find a function declared after the
+header. Diagnostics from these instantiations, including warnings, are
+issued when the precompiled header is created, and not again in the
+translation units that use it.
+
+This option has no effect when using a precompiled header. With
+@option{-fmodules}, @option{-x c++-header} creates a header unit, which
+always does these instantiations.
+
@opindex fno-pretty-templates
@opindex fpretty-templates
@item -fno-pretty-templates
@@ -38097,6 +38120,12 @@ used; they're searched in no particular order.
There are many other possibilities, limited only by your imagination,
good sense, and the constraints of your build system.
+In C++, the templates that the inline functions of a header use are
+normally instantiated at the end of each translation unit that uses its
+precompiled header. @xref{C++ Dialect Options,,-fpch-instantiate-templates},
+to do these instantiations once when creating the precompiled header
+instead.
+
A precompiled header file can be used only when these conditions apply:
@itemize
diff --git a/gcc/testsuite/g++.dg/pch/pch-instantiate-1.C
b/gcc/testsuite/g++.dg/pch/pch-instantiate-1.C
new file mode 100644
index 00000000000..4956d564e44
--- /dev/null
+++ b/gcc/testsuite/g++.dg/pch/pch-instantiate-1.C
@@ -0,0 +1,7 @@
+// The labels in the assembler output are numbered in the order in which the
+// functions are defined or instantiated. Instantiations done when writing the
+// PCH come before the definitions of this file, while without the PCH they
+// come at the end of it, so this file must not define functions for the
+// assembler output to be the same with and without the PCH.
+
+#include "pch-instantiate-1.H"
diff --git a/gcc/testsuite/g++.dg/pch/pch-instantiate-1.Hs
b/gcc/testsuite/g++.dg/pch/pch-instantiate-1.Hs
new file mode 100644
index 00000000000..3049241524e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/pch/pch-instantiate-1.Hs
@@ -0,0 +1,56 @@
+// { dg-options "-fpch-instantiate-templates" }
+// Templates used by the inline functions of the header are instantiated
+// before the PCH is written. The out-of-line function at the end of this
+// header uses them, so the code generated from those instantiations is
+// compared with the code generated without the PCH.
+
+template<typename T>
+T
+twice (T t)
+{
+ return t + t;
+}
+
+template<typename T, int N>
+struct sum
+{
+ static T get (T t) { return t + sum<T, N - 1>::get (t); }
+};
+
+template<typename T>
+struct sum<T, 0>
+{
+ static T get (T) { return T (); }
+};
+
+template<typename T>
+struct counter
+{
+ static int count;
+ static int bump () { return ++count; }
+};
+
+template<typename T>
+int counter<T>::count = 1;
+
+template<typename T>
+struct box
+{
+ T val;
+ T get () const { return twice (val) + sum<T, 3>::get (val); }
+ virtual T vget () const { return get (); }
+};
+
+inline int
+f (int i)
+{
+ box<int> b;
+ b.val = i;
+ return b.get () + b.vget () + twice (1.5) + counter<long>::bump ();
+}
+
+int
+g (int i)
+{
+ return f (i);
+}
diff --git a/gcc/testsuite/g++.dg/pch/pch-instantiate-2.C
b/gcc/testsuite/g++.dg/pch/pch-instantiate-2.C
new file mode 100644
index 00000000000..a6d6a7aba5d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/pch/pch-instantiate-2.C
@@ -0,0 +1 @@
+#include "pch-instantiate-2.H"
diff --git a/gcc/testsuite/g++.dg/pch/pch-instantiate-2.Hs
b/gcc/testsuite/g++.dg/pch/pch-instantiate-2.Hs
new file mode 100644
index 00000000000..fec36016fbf
--- /dev/null
+++ b/gcc/testsuite/g++.dg/pch/pch-instantiate-2.Hs
@@ -0,0 +1,15 @@
+// { dg-options "-fpch-instantiate-templates -Wsign-compare" }
+// Diagnostics from the instantiations are issued when the PCH is built.
+
+template<typename T>
+bool
+less (T a, unsigned b)
+{
+ return a < b; // { dg-warning "comparison of integer expressions of
different signedness" }
+}
+
+inline bool
+f (int i)
+{
+ return less (i, 1u);
+}
diff --git a/gcc/testsuite/g++.dg/pch/pch-instantiate-3.C
b/gcc/testsuite/g++.dg/pch/pch-instantiate-3.C
new file mode 100644
index 00000000000..25d00be3d87
--- /dev/null
+++ b/gcc/testsuite/g++.dg/pch/pch-instantiate-3.C
@@ -0,0 +1,11 @@
+// { dg-options "-fpch-instantiate-templates" }
+// -fpch-instantiate-templates is accepted, and does nothing, when a PCH is
+// only used.
+
+#include "pch-instantiate-3.H"
+
+int
+g (int i)
+{
+ return f (i) + twice (2L);
+}
diff --git a/gcc/testsuite/g++.dg/pch/pch-instantiate-3.Hs
b/gcc/testsuite/g++.dg/pch/pch-instantiate-3.Hs
new file mode 100644
index 00000000000..486521176a8
--- /dev/null
+++ b/gcc/testsuite/g++.dg/pch/pch-instantiate-3.Hs
@@ -0,0 +1,14 @@
+// The PCH is built without -fpch-instantiate-templates.
+
+template<typename T>
+T
+twice (T t)
+{
+ return t + t;
+}
+
+inline int
+f (int i)
+{
+ return twice (i);
+}
diff --git a/gcc/testsuite/g++.dg/pch/pch-instantiate-4.C
b/gcc/testsuite/g++.dg/pch/pch-instantiate-4.C
new file mode 100644
index 00000000000..eb7d617a230
--- /dev/null
+++ b/gcc/testsuite/g++.dg/pch/pch-instantiate-4.C
@@ -0,0 +1,11 @@
+// { dg-options "-g0" }
+// This file must not define functions, see pch-instantiate-1.C.
+
+#include "pch-instantiate-4.H"
+
+// Specializations that have already been instantiated in the PCH.
+template int twice<int> (int);
+extern template int thrice<int> (int);
+
+// { dg-final { scan-assembler "_Z5twiceIiEiT_:" } }
+// { dg-final { scan-assembler-not "_Z6thriceIiEiT_:" } }
diff --git a/gcc/testsuite/g++.dg/pch/pch-instantiate-4.Hs
b/gcc/testsuite/g++.dg/pch/pch-instantiate-4.Hs
new file mode 100644
index 00000000000..ee28a4cd52f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/pch/pch-instantiate-4.Hs
@@ -0,0 +1,34 @@
+// { dg-options "-fpch-instantiate-templates -g0" }
+// Explicit instantiation definitions and declarations after a header whose
+// instantiations were done when writing the PCH. With the PCH, thrice<int>
+// has been instantiated before its explicit instantiation declaration, which
+// shows in the debug information, hence -g0, and would allow inlining it,
+// hence noipa.
+
+template<typename T>
+int
+twice (T t)
+{
+ return t + t;
+}
+
+template<typename T>
+__attribute__ ((noipa)) int
+thrice (T t)
+{
+ return 3 * t;
+}
+
+inline int
+f (int i)
+{
+ return twice (i) + thrice (i);
+}
+
+// Without the explicit instantiation declaration of thrice<int> in the
+// translation unit, this would cause thrice<int> to be emitted.
+int
+g (int i)
+{
+ return f (i);
+}
diff --git a/gcc/testsuite/g++.dg/pch/pch-instantiate-5.C
b/gcc/testsuite/g++.dg/pch/pch-instantiate-5.C
new file mode 100644
index 00000000000..7fd31f5646a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/pch/pch-instantiate-5.C
@@ -0,0 +1,20 @@
+#include "pch-instantiate-5.H"
+
+// Specializations for arguments that the header does not use.
+template<>
+constexpr int
+size_of<char> (char)
+{
+ return 42;
+}
+
+template<>
+struct traits<long>
+{
+ static constexpr int id () { return 2; }
+};
+
+static_assert (size_of ('c') == 42, "");
+static_assert (size_of (1) == sizeof (int), "");
+static_assert (traits<long>::id () == 2, "");
+static_assert (traits<int>::id () == 1, "");
diff --git a/gcc/testsuite/g++.dg/pch/pch-instantiate-5.Hs
b/gcc/testsuite/g++.dg/pch/pch-instantiate-5.Hs
new file mode 100644
index 00000000000..67cd3897a2e
--- /dev/null
+++ b/gcc/testsuite/g++.dg/pch/pch-instantiate-5.Hs
@@ -0,0 +1,22 @@
+// { dg-options "-fpch-instantiate-templates" }
+// Specializations after a header whose instantiations were done when
+// writing the PCH.
+
+template<typename T>
+constexpr int
+size_of (T)
+{
+ return sizeof (T);
+}
+
+template<typename T>
+struct traits
+{
+ static constexpr int id () { return 1; }
+};
+
+inline int
+f (int i)
+{
+ return size_of (i) + traits<int>::id ();
+}
diff --git a/gcc/testsuite/g++.dg/template/pch-instantiate-1.C
b/gcc/testsuite/g++.dg/template/pch-instantiate-1.C
new file mode 100644
index 00000000000..030bcd58406
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/pch-instantiate-1.C
@@ -0,0 +1,22 @@
+// { dg-do assemble }
+// { dg-options "-S -x c++-header -fpch-instantiate-templates" }
+// dg-do assemble and -S rather than dg-do compile, for -x c++-header to come
+// before the input file, see g++.dg/header.C.
+// With -fpch-instantiate-templates, the templates used by the header are
+// instantiated at the end of the header, so names that are only declared
+// after it are not found.
+
+namespace N { struct S {}; }
+
+template<typename T>
+int
+call_h (T t)
+{
+ return h (t); // { dg-error "not declared in this scope" }
+}
+
+inline int
+use ()
+{
+ return call_h (N::S ());
+}
diff --git a/gcc/testsuite/g++.dg/template/pch-instantiate-2.C
b/gcc/testsuite/g++.dg/template/pch-instantiate-2.C
new file mode 100644
index 00000000000..fa102b06445
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/pch-instantiate-2.C
@@ -0,0 +1,21 @@
+// { dg-do assemble }
+// { dg-options "-S -x c++-header" }
+// dg-do assemble and -S rather than dg-do compile, for -x c++-header to come
+// before the input file, see g++.dg/header.C.
+// Without -fpch-instantiate-templates, the instantiation is deferred to the
+// translation units using the PCH, which may declare h after the header.
+
+namespace N { struct S {}; }
+
+template<typename T>
+int
+call_h (T t)
+{
+ return h (t);
+}
+
+inline int
+use ()
+{
+ return call_h (N::S ());
+}
diff --git a/gcc/testsuite/g++.dg/template/pch-instantiate-3.C
b/gcc/testsuite/g++.dg/template/pch-instantiate-3.C
new file mode 100644
index 00000000000..d6356338c45
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/pch-instantiate-3.C
@@ -0,0 +1,19 @@
+// { dg-do assemble { target c++11 } }
+// { dg-options "-S -x c++-header -fpch-instantiate-templates" }
+// dg-do assemble and -S rather than dg-do compile, for -x c++-header to come
+// before the input file, see g++.dg/header.C.
+// Errors in the instantiations are reported when the PCH is built.
+
+template<typename T>
+int
+f (T t)
+{
+ static_assert (sizeof (T) == 1, "T must be a character type"); // { dg-error
"static assertion failed" }
+ return t;
+}
+
+inline int
+g ()
+{
+ return f (1); // { dg-message "required from here" }
+}
diff --git a/gcc/testsuite/g++.dg/template/pch-instantiate-4.C
b/gcc/testsuite/g++.dg/template/pch-instantiate-4.C
new file mode 100644
index 00000000000..d8d6e64ea35
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/pch-instantiate-4.C
@@ -0,0 +1,24 @@
+// { dg-do assemble }
+// { dg-options "-S -x c++-header -fpch-instantiate-templates" }
+// dg-do assemble and -S rather than dg-do compile, for -x c++-header to come
+// before the input file, see g++.dg/header.C.
+// With -fpch-instantiate-templates, the templates used by the header are
+// instantiated at the end of the header, so argument-dependent lookup finds
+// the functions declared anywhere in the header.
+
+namespace N { struct S {}; }
+
+template<typename T>
+int
+call_h (T t)
+{
+ return h (t);
+}
+
+inline int
+use ()
+{
+ return call_h (N::S ());
+}
+
+namespace N { inline int h (S) { return 1; } }
--
2.47.1