| # C++ in Zircon | |
| A subset of the C++17 language is used in the Zircon tree. This includes both | |
| the kernel and userspace code. C++ is mixed with C (and some assembly) in | |
| both places. Some C++ language features are avoided or prohibited. Use of | |
| the C++ standard library features is very circumspect. | |
| ## Language features | |
| - Not allowed | |
| - Exceptions | |
| - RTTI and `dynamic_cast` | |
| - Operator overloading | |
| - Virtual inheritance | |
| - Statically constructed objects | |
| - Trailing return type syntax | |
| - Exception: when necessary for lambdas with otherwise unutterable return types | |
| - Initializer lists | |
| - `thread_local` in kernel code | |
| - Allowed | |
| - Pure interface inheritance | |
| - Lambdas | |
| - `constexpr` | |
| - `nullptr` | |
| - `enum class`es | |
| - `template`s | |
| - Default parameters | |
| - But use judgment. One optional out parameter at the end is | |
| probably fine. Four optional bool arguments, probably not. | |
| - Plain old classes | |
| - `auto` | |
| - Multiple implementation inheritance | |
| - But be judicious. This is used widely for e.g. intrusive | |
| container mixins. | |
| - Needs more ruling TODO(cpu) | |
| - Global constructors | |
| - Currently we have these for global data structures. | |
| **TODO:** pointer to style guide(s)? | |
| ## C++ Standard Edition (17 vs 14) | |
| Zircon code is built with `-std=c++17` and in general can use C++ 17 language | |
| and library features freely (subject to style/feature constraints described | |
| [above](#language-features) and library use guidelines described | |
| [below](#standard-library)). There is no *general* concern with staying | |
| compatible with C++ 14 or earlier versions. When a standard C++ 17 feature is | |
| the cleanest way to do something, do it that way. | |
| **However** any library that is **published to the IDK** must be compatible | |
| with IDK users building in **both** C++ 14 and C++ 17 modes. So, any | |
| libraries exported to the IDK must have public header files that are | |
| *compatible with both `-std=c++14` and `-std=c++17`*. If a library is | |
| exported to the IDK as source code rather than as a binary, then its *source | |
| code must also be completely compatible with both `-std=c++14` and | |
| `-std=c++17`* (and not require other special options). **TODO(mcgrathr):** | |
| _pointer to build-system docs about maintaining code to be exported to IDK_ | |
| All pure C code (`.c` source files and headers used by them) is C 11. Some | |
| special exceptions are made for code meant to be reused by out-of-tree boot | |
| loaders, which stick to a conservative C 89 subset for embedded code. | |
| ## Standard Library | |
| The C++ standard library API has many interfaces of widely varying | |
| characteristics. We subdivide the standard library API into several | |
| categories below, based on the predictability and complexity of each | |
| particular interface's code generation and use of machine and OS facilities. | |
| These can be thought of as widening concentric circles of the API from the | |
| most minimal C-like subset out to the full C++ 17 API. | |
| #### Context Matters | |
| This section gives guidelines for how to think about the impact of using a | |
| particular standard C++ library API on the system as a whole. There are no | |
| hard and fast rules, except for the kernel (see the next section)--and except | |
| for implementation constraints, which one always hopes should be temporary. | |
| The overwhelming rule is **be circumspect**. | |
| * Consider how well you understand the time and space complexity, the dynamic | |
| allocation behavior (if any), and the failure modes of *each API you use*. | |
| * Then consider the specific *context* where it's being used, and how | |
| sensitive that context is to those various kinds of concerns. | |
| * Be especially wary about **input-dependent** behavior that can quickly | |
| become far harder to predict when using nontrivial library facilities. | |
| If you're writing the main I/O logic in a driver, or anything that's in a hot | |
| path for latency, throughput, or reliability, in any kind of system service, | |
| then you should be pretty conservative in what library facilities you rely on. | |
| They're all technically available to you in userspace (though far fewer in the | |
| kernel; see the next section). But there's not so many you actually should | |
| use. You probably don't want to lean on a lot of `std` containers that do | |
| fancy dynamic allocation behind the scenes. They will make it hard for you to | |
| understand, predict, and control the storage/memory footprint, allocation | |
| behavior, performance, and reliability of your service. | |
| Nonetheless, even a driver is a userspace program that starts up and parses | |
| configuration files or arguments and so on. For all those nonessential or | |
| start-time functions that are not part of the hot path, using more complex | |
| library facilities is probably fine when that makes the work easier. Just | |
| remember to pay attention to overall metrics for your code, such as | |
| minimal/total/peak runtime memory use, code bloat (which uses both device | |
| storage and runtime memory), and resilience to unexpected failure modes. | |
| Maybe don't double the code size and memory footprint of your driver just to | |
| leverage that fancy configuration-parsing library. | |
| #### No `std` in kernel | |
| The C++ `std` namespace **cannot** be used in [kernel](/zircon/kernel) code, which | |
| also includes [bootloader](/zircon/bootloader). The few C++ standard library | |
| headers that don't involve `std::` APIs can still be used directly. See the | |
| next section. | |
| No other C++ standard headers should be used in kernel code. Instead, | |
| any library facilities worthwhile to have in the kernel (such as | |
| `std::move`) are provided via kernel-specific APIs (such as | |
| `ktl::move`). The kernel's implementations of these APIs may in fact | |
| rely on toolchain headers providing `std::` implementations that are | |
| aliased to kernel API names. But only those API implementations and | |
| very special cases in certain library headers should ever use `std::` | |
| in source code built into the kernel. | |
| #### Universal headers | |
| These header APIs are safe to use everywhere, even in the kernel. | |
| They include the C++ wrappers on the subset of standard C interfaces that the | |
| kernel supports: | |
| * [`<cstdarg>`](https://en.cppreference.com/w/cpp/header/cstdarg) | |
| * [`<cstddef>`](https://en.cppreference.com/w/cpp/header/cstddef) | |
| * [`<climits>`](https://en.cppreference.com/w/cpp/header/climits) | |
| * [`<cstdint>`](https://en.cppreference.com/w/cpp/header/cstdint) | |
| * [`<cinttypes>`](https://en.cppreference.com/w/cpp/header/cinttypes) | |
| * [`<cassert>`](https://en.cppreference.com/w/cpp/header/cassert) | |
| * [`<cstring>`](https://en.cppreference.com/w/cpp/header/cstring) | |
| The `std` namespace aliases for C library APIs from these headers should not | |
| be used in kernel code. | |
| One pure C++ header is also available even in the kernel: | |
| * [`<new>`](https://en.cppreference.com/w/cpp/header/new) | |
| The vanilla non-placement `operator new` and `operator new[]` are not | |
| available in the kernel. Use [`fbl::AllocChecker` | |
| `new`](/zircon/system/ulib/fbl/include/fbl/alloc_checker.h) instead. | |
| #### Conservative userspace | |
| These header APIs are safe to use everywhere. They're not allowed in the | |
| kernel because they're all entirely in the `std` namespace. But subsets of | |
| these APIs are likely candidates to get an in-kernel API alias if there is a | |
| good case for using such an API in kernel code. | |
| These are pure header-only types and templates. They don't do any dynamic | |
| allocation of their own. The time and space complexity of each function | |
| should be clear from its description. | |
| * [`<algorithm>`](https://en.cppreference.com/w/cpp/header/algorithm) | |
| * [`<array>`](https://en.cppreference.com/w/cpp/header/array) | |
| * [`<atomic>`](https://en.cppreference.com/w/cpp/header/atomic) | |
| * [`<bitset>`](https://en.cppreference.com/w/cpp/header/bitset) | |
| * [`<initializer_list>`](https://en.cppreference.com/w/cpp/header/initializer_list) | |
| * [`<iterator>`](https://en.cppreference.com/w/cpp/header/iterator) | |
| * [`<limits>`](https://en.cppreference.com/w/cpp/header/limits) | |
| * [`<optional>`](https://en.cppreference.com/w/cpp/header/optional) | |
| * [`<tuple>`](https://en.cppreference.com/w/cpp/header/tuple) | |
| * [`<type_traits>`](https://en.cppreference.com/w/cpp/header/type_traits) | |
| * [`<utility>`](https://en.cppreference.com/w/cpp/header/utility) | |
| * [`<variant>`](https://en.cppreference.com/w/cpp/header/variant) | |
| These involve some dynamic allocation, but only what's explicit: | |
| * [`<any>`](https://en.cppreference.com/w/cpp/header/any) | |
| * [`<memory>`](https://en.cppreference.com/w/cpp/header/memory) | |
| The `std::shared_ptr`, `std::weak_ptr`, and `std::auto_ptr` APIs should | |
| **never** be used. Use `std::unique_ptr` and `fbl::RefPtr` instead. | |
| ##### Userspace-only | |
| These are not things that would ever be available at all or by any similar API | |
| or name in the kernel. But they are generally harmless everywhere in | |
| userspace. They do not involve dynamic allocation. | |
| * Floating-point is never available in kernel code, but can be used | |
| (subject to performance considerations) in all userspace code. | |
| * [`<cfenv>`](https://en.cppreference.com/w/cpp/header/cfenv) | |
| * [`<cfloat>`](https://en.cppreference.com/w/cpp/header/cfloat) | |
| * [`<cmath>`](https://en.cppreference.com/w/cpp/header/cmath) | |
| * [`<complex>`](https://en.cppreference.com/w/cpp/header/complex) | |
| * [`<numeric>`](https://en.cppreference.com/w/cpp/header/numeric) | |
| * [`<ratio>`](https://en.cppreference.com/w/cpp/header/ratio) | |
| * [`<valarray>`](https://en.cppreference.com/w/cpp/header/valarray) | |
| * Full C 11 standard library, via C++ wrappers or in standard C `<*.h>`. | |
| * [`<csetjmp>`](https://en.cppreference.com/w/cpp/header/csetjmp) | |
| * [`<cstdlib>`](https://en.cppreference.com/w/cpp/header/cstdlib) | |
| * [Standard C11 interfaces](https://en.cppreference.com/w/c/header) | |
| * Synchronization and threads. These standard APIs are safe to use in all | |
| userspace code with appropriate discretion. But it may often be better to | |
| use Zircon's own library APIs for similar things, such as | |
| [<lib/sync/...>](/zircon/system/ulib/sync/include). | |
| * [`<condition_variable>`](https://en.cppreference.com/w/cpp/header/condition_variable) | |
| * [`<execution>`](https://en.cppreference.com/w/cpp/header/execution) | |
| * [`<mutex>`](https://en.cppreference.com/w/cpp/header/mutex) | |
| * [`<shared_mutex>`](https://en.cppreference.com/w/cpp/header/shared_mutex) | |
| * [`<thread>`](https://en.cppreference.com/w/cpp/header/thread) | |
| #### Kitchen sink | |
| These involve dynamic allocation that is hard to predict and is generally out | |
| of your control. The exact runtime behavior and memory requirements are often | |
| hard to reason about. Think very hard before using these interfaces in any | |
| critical path for reliability or performance or in any component that is meant | |
| to be lean and space-efficient. | |
| * The entire [Containers library](https://en.cppreference.com/w/cpp/container) | |
| * [`<functional>`](https://en.cppreference.com/w/cpp/header/functional) | |
| See [`<lib/fit/function.h>`](/zircon/system/ulib/fit/include/lib/fit/function.h) | |
| for a homegrown alternative. | |
| * [`<memory_resource>`](https://en.cppreference.com/w/cpp/header/memory_resource) | |
| * [`<scoped_allocator>`](https://en.cppreference.com/w/cpp/header/scoped_allocator) | |
| * [`<filesystem>`](https://en.cppreference.com/w/cpp/header/filesystem) | |
| * [`<regex>`](https://en.cppreference.com/w/cpp/header/regex) | |
| ## FBL | |
| FBL is the Fuchsia Base Library, which is shared between kernel and userspace. | |
| As a result, FBL has very strict dependencies. For example, FBL cannot depend | |
| on the syscall interface because the syscall interface is not available within | |
| the kernel. Similarly, FBL cannot depend on C library features that are not | |
| available in the kernel. | |
| 1. [system/ulib/fbl](/zircon/system/ulib/fbl) which is usable from both | |
| kernel and userspace. | |
| 2. [kernel/lib/fbl](/zircon/kernel/lib/fbl) which is usable only from | |
| the kernel. | |
| Note: Some FBL interfaces below that overlap with standard C++ library | |
| interfaces will probably be either removed entirely or made kernel-only (and | |
| perhaps renamed inside the kernel) once userspace code has migrated to using | |
| standard C++ library facilities where appropriate. | |
| FBL provides: | |
| - utility code | |
| - [basic algorithms](/zircon/system/ulib/fbl/include/fbl/algorithm.h) | |
| - [alloc checking new](/zircon/system/ulib/fbl/include/fbl/alloc_checker.h) | |
| - allocators | |
| - [slab allocation](/zircon/system/ulib/fbl/include/fbl/slab_allocator.h) | |
| - [slab malloc](/zircon/system/ulib/fbl/include/fbl/slab_malloc.h) | |
| - arrays | |
| - [fixed sized arrays](/zircon/system/ulib/fbl/include/fbl/array.h) | |
| - [fixed sized arrays](/zircon/system/ulib/fbl/include/fbl/inline_array.h), | |
| which stack allocates small arrays | |
| - inline containers | |
| - [doubly linked list](/zircon/system/ulib/fbl/include/fbl/intrusive_double_list.h) | |
| - [hash table](/zircon/system/ulib/fbl/include/fbl/intrusive_hash_table.h) | |
| - [singly linked list](/zircon/system/ulib/fbl/include/fbl/intrusive_single_list.h) | |
| - [wavl trees](/zircon/system/ulib/fbl/include/fbl/intrusive_wavl_tree.h) | |
| - smart pointers | |
| - [intrusive refcounted mixin](/zircon/system/ulib/fbl/include/fbl/ref_counted.h) | |
| - [intrusive refcounting pointer](/zircon/system/ulib/fbl/include/fbl/ref_ptr.h) | |
| - raii utilities | |
| - [auto call](/zircon/system/ulib/fbl/include/fbl/auto_call.h) to run | |
| code upon leaving scope | |
| - [AutoLock](/zircon/system/ulib/fbl/include/fbl/auto_lock.h) | |
| FBL has strict controls on memory allocation. Memory allocation should be | |
| explicit, using an AllocChecker to let clients recover from allocation | |
| failures. In some cases, implicit memory allocation is permitted, but | |
| functions that implicitly allocate memory must be #ifdef'ed to be unavailable | |
| in the kernel. | |
| FBL not available outside the Platform Source Tree. | |
| ## ZX | |
| ZX contains C++ wrappers for the Zircon [objects](/docs/reference/kernel_objects/objects.md) and | |
| [syscalls](/docs/reference/syscalls/README.md). These wrappers provide type safety and move semantics | |
| for handles but offer no opinion beyond what's in syscalls.abigen. At some | |
| point in the future, we might autogenerate ZX from syscalls.abigen, similar to | |
| how we autogenerate the syscall wrappers in other languages. | |
| ZX is part of the Fuchsia SDK. | |
| ## FZL | |
| FZL is the Fuchsia Zircon Library. This library provides value-add for common | |
| operations involving kernel objects and is free to have opinions about how to | |
| interact with the Zircon syscalls. If a piece of code has no dependency on | |
| Zircon syscalls, the code should go in FBL instead. | |
| FZL not available outside the Platform Source Tree. | |
| ## Hermetic C++ | |
| We encourage using C++ rather than C as the implementation language throughout | |
| Fuchsia. However, in many instances we require a narrow ABI bottleneck to | |
| simplify the problem of preventing, tracking, or adapting to ABI drift. The | |
| first key way to keep the ABI simple is to base it on a pure C API (which can | |
| be used directly from C++, and via foreign-function interfaces from many other | |
| languages) rather than a C++ API. When we link together a body of code into a | |
| module with a pure C external API and ABI but using C++ internally for its | |
| implementation, we call that _hermetic C++_. | |
| * The kernel itself could be said to be implemented in hermetic C++. | |
| * The [vDSO](/docs/concepts/kernel/vdso.md) is a shared library implemented in hermetic C++. | |
| * Fuchsia's [standard C library](/zircon/system/ulib/c), while largely implemented | |
| in C, also uses hermetic C++ in its implementation. | |
| * Most Fuchsia device drivers are implemented in hermetic C++. | |
| It's a hard and fast rule for binaries exported in the Fuchsia's public SDK | |
| that **shared libraries must have a pure C API and ABI**. Such libraries can | |
| _and should_ use C++ rather than C in their implementations, and they can use | |
| other *statically-linked* libraries with C++ APIs as long as ABI aspects of | |
| those internal C++ APIs don't leak out into the shared library's public ABI. | |
| A "loadable module" (sometimes called a "plug-in" module) is very similar to a | |
| shared library. The same rules about pure a C ABI bottleneck apply for | |
| loadable module ABIs. Fuchsia device drivers are just such loadable modules | |
| that must meet the driver (pure C) ABI. Hence, every driver implemented in | |
| C++ must use hermetic C++. | |
| The Fuchsia C++ toolchain provides the full C++17 standard library using the | |
| [libc++](https://libcxx.llvm.org/) implementation. In C++ executables (and | |
| shared libraries with a C++ ABI) this is usually dynamically linked, and | |
| that's the default behavior of the compiler. The toolchain also provides | |
| `libc++` for *hermetic static linking* via the `-static-libstdc++` switch to | |
| the compiler (`clang++`). In the Zircon GN build system, a linking target | |
| such as `executable()`, `test()`, or `library()` (with `shared = true`), uses | |
| this line to request the hermetic C++ standard library: | |
| ```gn | |
| configs += [ "$zx/public/gn/config:static-libc++" ] | |
| ``` | |
| This is **required** in each `library()` that is exported to the public IDK | |
| _in binary form_ via `sdk = "shared"`. | |
| Every `driver()` automatically uses hermetic C++ and so this line is not | |
| required for them. (Drivers cannot depend on their own shared libraries, only | |
| the dynamic linking environment provided by the driver ABI.) | |
| For executables and non-exported shared libraries, it's a judgment call | |
| whether to use static linking or dynamic linking for the standard C++ library. | |
| In Fuchsia's package deployment model, there is no particular updatability | |
| improvement to using shared libraries as in many other systems. The primary | |
| trade-off is between the savings in memory and storage from many stored | |
| packages and running processes on the system using exactly the same shared | |
| library binary and compactness and (sometimes performance) of the individual | |
| package. Since many packages in the system build will all use the same shared | |
| `libc++` library, that's usually the right thing to do unless there are | |
| special circumstances. It's the default in the compiler and build system. |