Skip to content

Instantly share code, notes, and snippets.

@EricWF
Last active May 27, 2024 22:08
Show Gist options
  • Save EricWF/f61d97922767f743590782887fbb9c8d to your computer and use it in GitHub Desktop.
Save EricWF/f61d97922767f743590782887fbb9c8d to your computer and use it in GitHub Desktop.

Introduction

This document provides a detailed exploration of how C++ standard library headers are used when building, testing, and using the LLVM libc++ library. It covers the different types of headers involved, the main header layouts used, and the importance of include paths and how they are constructed. It also dives into the specific include paths used in key scenarios on Linux and Apple platforms. The goal is to explain the key concepts and complexities around libc++ headers to support informed decision making about the library's header layout.

The Headers

There are several types of headers involved in the libc++ library:

  1. The primary headers: These include all of the non-generated headers that are part of the standard library, such as <__config>, <vector>, and module.modulemap.

  2. <__config_site>: This is a configuration header generated by the build system, used to configure libc++ for the target platform. It contains key macro definitions.

  3. <__assertion_handler>: This header is copied verbatim from libcxx/vendor/llvm/default_assertion_handler.in unless overridden by the user via LIBCXX_ASSERTION_HANDLER_FILE during CMake configuration.

  4. <cxxabi.h> (and <__cxxabi_config.h> for libc++abi): These headers are copied from the ABI library chosen at configuration time, such as libc++abi, libsupc++/libstdc++, or libcxxrt.

This means that in the two-directory layout, the only differences in contents between the in-tree and build-tree primary include directorys is cxxabi.h, and until #93333 lands, __assertion_handler. Otherwise, they're functionally identical.

The Two Header Layouts

Libc++ supports two main layouts for the headers as copied into the build directory:

  1. Single-directory layout: All headers, including generated ones, are placed in a single directory, <include-root>/c++/v1.
  2. Two-directory layout: Headers are split between <include-root>/<triple>/c++/v1 for target-specific headers and <include-root>/c++/v1 for the rest.

The two-directory layout is used by default on Linux, while the single-directory layout is used on Apple platforms.

Why Include Paths Matter

The include paths are critical for two main reasons:

  1. Libc++'s use of #include_next
  2. Ensuring we "test as we ship to users"

#include_next Basics

Libc++ uses #include_next to wrap C standard library headers to make adjustments for C++. For example, the libc++ <stddef.h> uses #include_next to include the C version and then adds the ::nullptr_t type.

#include_next tells the preprocessor to search for the specified header in the include paths, starting from the path after the one in which the current file was found. If the headers are not laid out correctly, this can lead to the wrong headers being included.

Here's an example of how #include_next works:

// libc++ <inttypes.h>
#include_next <inttypes.h>

// Clang <inttypes.h>  
#include_next <inttypes.h>

// System <inttypes.h>
...

If the include paths are not ordered correctly, with libc++ first, Clang second, and system headers last, the #include_next chain will break.

How Clang Constructs the Include Paths

When building and testing libc++, the include paths are constructed to mirror the installed header layout. The exact paths depend on the Clang driver used.

On Linux, the driver searches for c++/v1 in the following prefixes:

  1. <clang-binary-path>/../include/
  2. /usr/local/include
  3. /usr/include

If found, it adds -internal-isystem <libcxx-include-prefix>/c++/v1 to the compile command. If a <triple>/c++/v1 directory exists, it is added before the main path.

On Apple, the driver looks in:

  1. <clang-binary-path>/../include/c++/v1
  2. <sysroot>/usr/include/c++/v1

It does not search for a target-specific directory.

The Include Paths Used in Practice

Building on Linux

When building libc++ on Linux, the command line contains:

-nostdinc++ 
-I <src-root>/libcxx/src 
-I <src-root>/build/include/x86_64-unknown-linux-gnu/c++/v1 
-I <src-root>/build/include/c++/v1  
-I <src-root>/libcxxabi/include

the include paths are:

#include "..." search starts here:
#include <...> search starts here:
 /home/eric/llvm-project/libcxx/src
 /home/eric/llvm-project/build/include/x86_64-unknown-linux-gnu/c++/v1
 /home/eric/llvm-project/build/include/c++/v1
 /home/eric/llvm-project/libcxxabi/include
 /usr/local/lib/clang/19/include
 /usr/local/include
 /usr/include/x86_64-linux-gnu
 /usr/include

Testing on Linux

When running tests on Linux, the command line contains:

-nostdinc++ 
-I <src-root>/build/include/c++/v1 
-I <src-root>/build/include/x86_64-unknown-linux-gnu/c++/v1 
-I <src-root>/libcxx/test/support

the include paths are:

#include "..." search starts here:  
#include <...> search starts here:
 /home/eric/llvm-project/build/include/c++/v1
 /home/eric/llvm-project/build/include/x86_64-unknown-linux-gnu/c++/v1
 /home/eric/llvm-project/libcxx/test/support
 /usr/local/lib/clang/19/include
 /usr/local/include
 /usr/include/x86_64-linux-gnu
 /usr/include

Installing on Linux

For an installed libc++ on Linux, the include paths with -stdlib=libc++ are:

#include "..." search starts here:
#include <...> search starts here:
 /usr/local/bin/../include/x86_64-unknown-linux-gnu/c++/v1
 /usr/local/bin/../include/c++/v1
 /usr/local/lib/clang/19/include
 /usr/local/include
 /usr/include/x86_64-linux-gnu
 /usr/include

Building on Apple

When building on Apple, the command line contains:

-nostdinc++ 
-I <src-root>/libcxx/src 
-I <src-root>/build/include/c++/v1  
-I <src-root>/libcxxabi/include

and the include paths are:

#include "..." search starts here:
#include <...> search starts here:
 /Users/eric/llvm-project/libcxx/src
 /Users/eric/llvm-project/build/include/c++/v1
 /Users/eric/llvm-project/libcxxabi/include
 /Library/Developer/CommandLineTools/usr/lib/clang/15.0.0/include
 /Library/Developer/CommandLineTools/SDKs/MacOSX14.4.sdk/usr/include
 /Library/Developer/CommandLineTools/usr/include
 /Library/Developer/CommandLineTools/SDKs/MacOSX14.4.sdk/System/Library/Frameworks (framework directory)

Testing on Apple

When running tests on Apple, the command line contains:

-nostdinc++ 
-I <src-root>/build/include/c++/v1 
-I <src-root>/build/include/c++/v1 
-I <src-root>/libcxx/test/support

and the final include paths are:

#include "..." search starts here:
#include <...> search starts here:
 /Users/eric/llvm-project/build/include/c++/v1
 /Users/eric/llvm-project/libcxx/test/support
 /Library/Developer/CommandLineTools/usr/lib/clang/15.0.0/include
 /Library/Developer/CommandLineTools/SDKs/MacOSX14.4.sdk/usr/include
 /Library/Developer/CommandLineTools/usr/include
 /Library/Developer/CommandLineTools/SDKs/MacOSX14.4.sdk/System/Library/Frameworks (framework directory)

Installing on Apple

For an installed libc++ on Apple, the include paths with -stdlib=libc++ are:

#include "..." search starts here:
#include <...> search starts here:
 /usr/local/include
 /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1 
 /Library/Developer/CommandLineTools/usr/lib/clang/15.0.0/include
 /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include
 /Library/Developer/CommandLineTools/usr/include
 /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks (framework directory)

Key Takeaways

  • Libc++ supports two main header layouts in the build directory: single-directory and two-directory.
  • The include paths are critical due to libc++'s use of #include_next and the need to "test as you fly".
  • The exact include paths depend on the platform and Clang driver, but in general, libc++ headers must come first, followed by Clang, then system headers.
  • On both Linux and Apple, the include paths used when building and testing libc++ are different than for an installed libc++.
  • The single-directory layout is simpler and avoids some bugs that can occur with the two-directory layout.
  • However, neither the single-directory nor two-directory build layouts exactly match the include paths of an installed libc++.

Libc++ should consider how to balance supporting development and testing with ensuring the installed header layout is properly validated. Improvements to the current system are likely possible to enhance simplicity and correctness.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment