IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Precompiled headers

    MaskRay发表于 2023-07-24 02:50:00
    love 0

    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:

    • Precompiled headers share implementation aspects with modules (e.g.,AST serialization in Clang).
    • Many C++ projects rely on the traditional compilation model and arenot converted to C++ modules.
    • Modules may possibly use some preamble-like technology to accelerateIDE-centric operations.
    • C doesn't have C++ modules.

    This article focuses on Clang precompiled headers (PCH). Let's beginwith an example.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    cat > a1.cc <<'eof'
    #include "b.hh"
    int fa1() { return fb(); }
    eof
    cat > a.cc <<'eof'
    #include "b.hh"
    int fa1();
    int main() { return fa1() + fb(); }
    eof
    cat > b.hh <<'eof'
    #ifndef B_HH
    #define B_HH
    inline int fb() { return 42; }
    #endif
    eof

    clang -c b.hh -o b.hh.pch # cc1 action is -emit-pch
    clang -c -include-pch b.hh.pch a.cc # or -include b.hh
    clang -c -include-pch b.hh.pch a1.cc
    clang++ a.o a1.o -o a

    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.hhshould have a header guard or use #pragma once.

    Now, let's examine the steps in detail.

    PCH generation

    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 clangand 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-pchfrontend 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.

    Using PCH

    -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
    2
    3
    4
    5
    6
    7
    echo 'extern int X;' > d.hh
    echo 'int use() { return X; }' > e.cc
    mkdir d.hh.pch
    clang++ -c -DX=x d.hh -o d.hh.pch/x.pch
    clang++ -c -DX=y d.hh -o d.hh.pch/y.pch
    clang++ -c -DX=x -include-pch d.hh.pch e.cc # use d.hh.pch/x.pch
    clang++ -c -DX=x -include d.hh e.cc # use d.hh.pch/x.pch
    1
    2
    3
    4
    5
    6
    % clang++ -c -DX=z -include d.hh e.cc
    error: no suitable precompiled header file found in directory 'd.hh.pch'
    1 error generated.
    % clang++ -c -include d.hh e.cc # clang>=15.0
    error: no suitable precompiled header file found in directory 'd.hh.pch'
    1 error generated.

    PCH validation

    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_LANGOPToptions (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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    clang -c b.hh -o b.hh.pch -DB=1

    # tolerable
    clang -c -include-pch b.hh.pch a.cc

    # definition of macro 'B' differs between the precompiled header ('1') and the command line ('2')
    clang -c -include-pch b.hh.pch a.cc -DB=2

    # error: macro 'B' was defined in the precompiled header but undef'd on the command line
    clang -c -include-pch b.hh.pch a.cc -UB

    As an escape hatch, -Xclang -fno-validate-pch disablesPCH validation.

    Performance optimization

    In order to achieve better performance, it is possible to makecertain compromises on properties such as language standardconformance.

    -fpch-instantiate-templates

    -fpch-instantiate-templatesallows 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
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #ifndef HEADER
    #define HEADER
    template<typename T> void f();
    struct X;
    void g() { f<X>(); } // delayed instantiation

    template<typename T> void f() { T t; };
    #else
    struct X {};
    #endif
    1
    2
    3
    4
    5
    6
    7
    % clang++ -c -xc++-header a.cc -o a.pch
    % clang++ -c -include-pch a.pch a.cc
    % clang++ -c -xc++-header a.cc -o a.pch -fpch-instantiate-templates
    d.cc:5:35: error: variable has incomplete type 'X'
    5 | template<typename T> void f() { T t; };
    | ^
    ...

    -fpch-instantiate-templates is the default when usingthe clang-cl driver mode, as MSVC appears to have a similarbehavior.

    Modular code generation

    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
    7
    echo '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
    5
    clang++ -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.

    MSVC-style precompiledheaders

    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
    2
    clang-cl /c /Ycb.hh a.cc
    clang-cl /c /Yub.hh a1.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.ccusing -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 /Yuwithout 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

    Precompiled preamble

    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
    2
    3
    4
    clang -c b.hh -o b.hh.pch
    clang -c -include-pch b.hh.pch a.cc
    touch b.hh
    clang -c -include-pch b.hh.pch a.cc # error: file '/tmp/d/b.hh' has been modified since the precompiled header 'b.hh.pch' was built: mtime changed (was 1689570104, now 1689576446)

    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.



沪ICP备19023445号-2号
友情链接