C/C++ projects can benefit from using precompiled headers to improvecompile time. GCC addedsupport for precompiled headers in 2003 (version 3.4), andthe current documentation can be found at https://gcc.gnu.org/onlinedocs/gcc/Precompiled-Headers.html.
Even with the emergence of C++ modules, precompiled headers remainrelevant for several reasons:
This article focuses on Clang precompiled headers (PCH). Let's beginwith an example.
1 | cat > a1.cc <<'eof' |
We compile b.hh
using -c
, just like wewould compile a non-header file. Clang parses the file, performssemantic analysis, and writes the precompiled header (as a serializedAST file) into b.hh.pch
.
When compiling a.cc
, we use -include-pch
asa prefix header. This means that the translation unit will get twob.hh
copies: one from b.hh.pch
and one fromthe textual b.hh
. The same applies to a1.cc
.To avoid a redefinition of 'fb'
error, b.hh
should have a header guard or use #pragma once
.
Now, let's examine the steps in detail.
Given a header file as input, Clang determines the input type aseither c-header
(.h
) orc++-header
(.hh
/.hpp
/.hxx
) based on the fileextension.
For compilation actions, either clang
orclang++
can be used. If we treat .h
as a C++header, we need to specify -xc++-header
(e.g.,clang -c -xc++-header b.h -o b.h.pcm
). (It's worth notingthat the behavior of clang++ -c a.h
is deprecated. Otherthan that, the only significant difference between clang
and clang++
is the linking process, specifically whetherthe C++ standard library is linked.)
When the input type is c-header
orc++-header
, Clang Driver selects the -emit-pch
frontend action. (Note:c++-user-header
/c++-system-header
are used forC++ modules and have different functionality.)
Conventionally, the extension used for Clang precompiled headers is.pch
(similar to MSVC). However, to match GCC, when the-o
option is omitted, the default output file isinput_file + ".gch"
(seeDriver::GetNamedOutputPath
). For example, when the inputfile is d/b.hh
, the output is d/b.hh.gch
. Thisis different from d/b.cc
that will go tob.o
.
The frontend parses the file, performs semantic analysis, and writesthe precompiled header (as a serialized AST file) (seePCHGenerator
). For the serialized format, refer to Precompiled Headerand Modules Internals.
-include-pch b.hh.pch
(PreprocessorOptions::ImplicitPCHInclude
) loads theprecompiled header b.hh.pch
as a prefix header.
We can also write -include b.hh
, and Clang will probeb.hh.pch
/b.hh.gch
and use the file if present.This is a behavior ported from GCC.
-include
and -include-pch
may specify adirectory. Clang will search for a suitable precompiled header in thedirectory (see ASTReader::isAcceptableASTFile
). Thedirectory may contain precompiled headers for different compileroptions. This is another behavior ported from GCC.
1 | echo 'extern int X;' > d.hh |
1 | % clang++ -c -DX=z -include d.hh e.cc |
When we generate and use a precompiled header with different compileroptions, the behavior will be a combination of those options.Consequently, the behavior of -include b.hh
may differdepending on the presence ofb.hh.pch
/b.hh.gch
.
To identify this common pitfall, Clang performs PCH validation (seePCHValidator
) to check for inconsistent options, similar tohow MSVC handles it. The validated options include those that can affectAST generation, such as language options (driver -std=
,-fPIC
, -fPIE
), target options (driver--target
), file system options, header search options, andpreprocessor options.
Modules employ the same validation mechanism, but PCH validation isstricter (!AllowCompatibleConfigurationMismatch
). Thismeans thatCOMPATIBLE_LANGOPT
/COMPATIBLE_ENUM_LANGOPT
/COMPATIBLE_VALUE_LANGOPT
options (e.g., whether the built-in macro __OPTIMIZE__
isdefined) must match as well.
If one side of the precompiled header and the user code are compiledwith the -D
option, the other side should either use thesame -D
option or omit it entirely.
1 | clang -c b.hh -o b.hh.pch -DB=1 |
As an escape hatch, -Xclang -fno-validate-pch
disablesPCH validation.
In order to achieve better performance, it is possible to makecertain compromises on properties such as language standardconformance.
-fpch-instantiate-templates
-fpch-instantiate-templates
allows pending template instantiations to be performed in the PCH file.This means that these instantiations do not need to be repeated in everytranslation unit that includes the PCH file. This optimization cansignificantly improve the speed of certain projects. However, the optionchanges the point of instantiation for certain function templates, whichis non-conforming. Nevertheless, the altered behavior is generallyharmless in most cases.
1 |
|
1 | % clang++ -c -xc++-header a.cc -o a.pch |
-fpch-instantiate-templates
is the default when usingthe clang-cl driver mode, as MSVC appears to have a similarbehavior.
Modular code generation was initially implemented. It waslater extended to supportprecompiled headers in Clang 11. To utilize this feature, you canspecify -Xclang -fmodules-codegen
as a command-line optionor use the driver option -fpch-codegen
.
When generating a serialized AST file for PCH or modules, Clangidentifies non-always-inline functions that do not depend on templateparameters and have linkages other than GVA_Internal
orGVA_AvailableExternally
. These functions are thenserialized (see ASTWriter::ModularCodegenDecls
).
In an importer that encounters such a definition, the linkage isadjusted to GVA_AvailableExternally
. This allows fordiscarding of the definition if it is not required foroptimizations.
Let's consider an example using the files a.cc
,a1.cc
, and b.hh
from the initial exampleprovided at the beginning of this article. 1
2
3
4
5
6
7echo 'module b { header "b.hh" }' > module.modulemap
clang -c -Xclang -emit-module -fmodules -xc++ module.modulemap -fmodule-name=b -o b.pcm -Xclang -fmodules-codegen
clang -c b.pcm
clang -c -fmodules -fno-implicit-module-maps -fmodule-file=b.pcm a.cc
clang -c -fmodules -fno-implicit-module-maps -fmodule-file=b.pcm a1.cc
clang++ a.o a1.o b.o -o a
Both a.cc
and a1.cc
includeb.hh
and obtain an inline definition of fb
. Ina regular build, the fb
definition hasGVA_DiscardableODR
linkage and is compiled twice intoa.o
and a1.o
. These duplicate definitions arethen deduplicated by the linker, following COMDAT semantics.
In a modular code generation build, fb
is assignedGVA_StrongODR
linkage in b.pcm
and is emittedinto b.o
. The copies of fb
ina.cc
and a1.cc
are adjusted toGVA_AvailableExternally
. They are used for optimizations bycallers but are not emitted otherwise. In a -O0
build, theGVA_AvailableExternally
definitions are simply discarded.Regardless, both the code generator and the linker have reduced work,resulting in decreased build time.
However, there are two primary differences in behavior.
First, if b.hh
contains aGVA_StrongExternal
definition, a regular build willencounter a linker error due to a duplicate symbol. However, in theprebuilt modules build using -fmodules-codegen
, this errordoes not occur.
Second, in a regular build, if fb
is unused, notranslation unit will contain its COMDAT definition. On the other hand,in the prebuilt modules build using -fmodules-codegen
, wecompile the prebuilt module b.pcm
into b.o
andlink b.o
into the executable, always resulting in adefinition of fb
, even when using -O1
orabove. To discard fb
, linker garbage collection can beleveraged by using -Wl,--gc-sections
(-ffunction-sections
is unneeded even for ELF targets,since COMDAT functions are emitted in separate sections anyway).
If b.hh
contains an inline variable with an initializerinvolving a side effect (e.g.,inline int vb = puts("vb"), 1;
), the modular codegeneration build will always observe the side effect. In contrast, aregular build may not observe the side effect if, for example, thecontaining header is not included in any translation unit.
Nevertheless, these behavior differences are almost always benign,and the speedup gained in build time may outweigh the downsides.
Note: Clang exhibits a similiar behavior when compiling moduleinterface units and module partitions for strong definitions.
Modular code generation has been extended to support PCH in Clang 11.We specify -fpch-codegen
to pass-fmodules-codegen
to the frontend. 1
2
3
4
5clang++ -c -fpch-codegen b.hh -o b.hh.pch
clang++ -c b.hh.pch -o b.o
clang++ -c -include-pch b.hh.pch a.cc
clang++ -c -include-pch b.hh.pch a1.cc
clang++ a.o a1.o b.o -o a
When using -fpch-codegen
, compared to thenon--fpch-codegen
usage of PCH, it is necessary to compilethe PCH file b.pch
into b.o
and linkb.o
into the executable. If b.o
is not linked,a linker error for an undefined fb()
will occur.
Here is a CMake featurerequest for -fpch-codegen
.
-Xclang -fmodules-codegen
The cc1 option -fmodules-debuginfo
serves a similarpurpose as -fmodules-codegen
, but specifically for debuginformation descriptions of CXXRecordDecl
that isnon-dependent. When using -g
, the compiled PCH or modulefile emit type definitions while importers just emit declarations(DIFlagFwdDecl
).
-fpch-debuginfo
instructs Clang Driver to pass-fmodules-codegen
to the frontend.
Here is an example of using MSVC-style precompiled headers withclang-cl (a CL-style driver mode), using the files a.cc
,a1.cc
, and b.hh
from the initial exampleprovided at the beginning of this article.
1 | clang-cl /c /Ycb.hh a.cc |
The /Ycb.hh
command instructs the Clang Driver toperform two frontend actions. First, Clang parses the base source filea.cc
up to and including #include "b.hh"
,performs semantic analysis, and writes the precompiled header intob.pch
. It replaces the header file extension with.pch
, unlike GCC. Second, Clang compiles a.cc
using -include-pch b.pch
, but it skips preprocessing tokensup to and including #include "b.hh"
(seePreprocessor::SkippingUntilPCHThroughHeader
).
The /Yub.hh
command is similar to the second frontendaction of /Ycb.hh
. It compiles a1.cc
using-include-pch b.pch
, but it also skips preprocessing tokensup to and including #include "b.hh"
.
Internally, /Ycb.hh
and /Yub.hh
instructthe driver to pass -pch-through-header=b.hh
to thefrontend. This helps Clang detect common pitfalls by examining whetherthe source file contains the #include "b.hh"
directive.
It is also possible to use /Yc
and /Yu
without specifying a filename. In this case, the precompiled headerregion is determined by #pragma hdrstop
or the end of thesource file. For more details, refer to /Yc(Create Precompiled Header File).
In MSVC, aninline function with the dllexport attribute is exported, whether ornot it is referenced. LLVM IR models such a definition with theweak_odr
linkage. When using clang-cl /Yc
, thecc1 option -building-pch-with-obj
is passed to thefrontend. This option instructs the frontend to serialize inlinefunctions with the dllexport attribute, similar to-fmodules-codegen
. In an importer that encounters such adefinition, the linkage is adjusted toGVA_AvailableExternally
.
See also /Zc:dllexportInlines-
.
TODO: PCH signature and linker
During IDE-centric operations, such as code completion and reportingdiagnostics, an approach called precompiled preamble (introduced toClang in 2010) is used to minimize the need for re-parsing the entirefile after making changes. The precompiled preamble is the initialregion of the source file that includes only preprocessor tokens andcomments. It is precompiled and then reused to speed up subsequentreparsing.
A precompiled preamble can be serialized to disk, similar toprecompiled headers.
-Xclang -fno-pch-timestamp
By default, the generated PCH file includes the modified time of theheaders it depends on. When using the PCH file, PCH validation checkswhether the included headers have changed based on their sizes andmodified time.
1 | clang -c b.hh -o b.hh.pch |
This timestamp safeguard, however, renders builds non-reproducibleand can cause inconvenience for distributed build systems.
The cc1 option -fno-pch-timestamp
can be used to disabletimestamp for PCH generation and bypass the timestamp validation.
For distributed build systems, precompiled headers have another majorissue. They are usually much larger than the source they are built from.The network traffic can offset some advantages.