pw_toolchain#
Embedded toolchains for GN-based Pigweed projects
Stable GN
GN toolchains function both as a set of tools for compilation and as a workspace for evaluating build files. The same compilations and actions can be executed by different toolchains. Each toolchain maintains its own set of build args, and build steps from all toolchains can be executed in parallel.
C/C++ toolchain support libraries#
pw_toolchain
provides some toolchain-related C/C++ libraries.
std:abort
wrapper#
The std::abort function is used to terminate a program abnormally. This function may be called by standard library functions, so is often linked into binaries, even if users never intentionally call it.
For embedded builds, the abort
implementation likely does not work as
intended. For example, it may pull in undesired dependencies (e.g.
std::raise
) and end in an infinite loop.
pw_toolchain
provides the pw_toolchain:wrap_abort
library that replaces
abort
in builds where the default behavior is undesirable. It uses the
-Wl,--wrap=abort
linker option to redirect to abort
calls to
PW_CRASH
instead.
arm-none-eabi-gcc support#
Targets building with the GNU Arm Embedded Toolchain (arm-none-eabi-gcc
)
should depend on the pw_toolchain/arm_gcc:arm_none_eabi_gcc_support
library. In GN, that target should be included in pw_build_LINK_DEPS
. In
Bazel, it should be added to link_extra_lib or
directly to the deps of any binary being build with that toolchain:
cc_binary(
deps = [
# Other deps, omitted
] + select({
"@platforms//cpu:armv7e-m": [
"@pigweed//pw_toolchain/arm_gcc:arm_none_eabi_gcc_support",
],
"//conditions:default": [],
}),
)
Newlib OS interface#
Newlib, the C Standard Library
implementation provided with arm-none-eabi-gcc
, defines a set of OS
interface functions that
should be implemented. Newlib provides default implementations, but using these
results in linker warnings like the following:
readr.c:(.text._read_r+0x10): warning: _read is not implemented and will always fail
Most of the OS interface functions should never be called in embedded builds.
The pw_toolchain/arg_gcc:newlib_os_interface_stubs
library, which is
provided through pw_toolchain/arm_gcc:arm_none_eabi_gcc_support
, implements
these functions and forces a linker error if they are used. It also
automatically includes a wrapper for abort
for use of stdout
and
stderr
which abort if they are called.
If you need to use your own wrapper for abort
, include the library directly
using pw_toolchain/arm_gcc:newlib_os_interface_stubs
.
Global variables: constant initialization and binary size#
Global variables—variables with static storage duration—are initialized
either during compilation (constant initialization) or at runtime.
Runtime-initialized globals are initialized before main
; function static
variables are initialized when the function is called the first time.
Constant initialization is guaranteed for constinit
or constexpr
variables. However, the compiler may constant initialize any variable, even if
it is not constinit
or constexpr
constructible.
Constant initialization is usually better than runtime initialization. Constant initialization:
Reduces binary size. The binary stores initialized variable in the binary (e.g. in
.data
or.rodata
), instead of the code needed to produce that data, which is typically larger.Saves CPU cycles. Initialization is a simple
memcpy
.Avoids the static initialization order fiasco. Constant initialization is order-independent and occurs before static initialization.
Constant initialization may be undesirable if initialized data is larger than
the code that produces it. Variables that are initialized to all 0s are
placed in a zero-initialized segment (e.g. .bss
) and never affect binary
size. Non-zero globals may increase binary size if they are constant
initialized, however.
Should I constant initialize?#
Globals should usually be constant initialized when possible. Consider the following when deciding:
If the global is zero-initialized, make it
constinit
orconstexpr
if possible. It will not increase binary size.If the global is initialized to anything other than 0 or
nullptr
, it will occupy space in the binary.If the variable is small (e.g. a few words), make it
constinit
orconstexpr
. The initialized variable takes space in the binary, but it probably takes less space than the code to initialize it would.If the variable is large, weigh its size against the size and runtime cost of its initialization code.
There is no hard-and-fast rule for when to constant initialize or not. The decision must be considered in light of each project’s memory layout and capabilities. Experimentation may be necessary.
Example
// This function initializes an array to non-zero values.
constexpr std::array<uint8_t, 4096> InitializedArray() {
std::array<uint8_t, 4096> data{};
for (size_t i = 0; i < data.size(); ++i) {
data[i] = static_cast<uint8_t>(i);
}
return data;
}
// This array constant initialized, which increases the binary size by 4KB.
constinit std::array<uint8_t, 4096> constant_initialized = InitializedArray();
// This array is statically initialized and takes no space in the binary, but
// the InitializedArray() function is included in the binary.
pw::RuntimeInitGlobal<std::array<uint8_t, 4096>> runtime_initialized(
InitializedArray());
// This array is zero-initialized and takes no space in the binary. It must be
// manually initialized.
std::array<uint8_t, 4096> zero_initialized;
Note
Objects cannot be split between .data
and .bss
. If an object contains
a single bool
initialized to true
followed by a 4KB array of zeroes,
it will be placed in .data
, and all 4096 zeroes will be present in the
binary.
A global pw::Vector
works like this. A default-initialized
pw::Vector<char, 4096>
includes one non-zero uint16_t
. If constant
initialized, the entire pw::Vector
is stored in the binary, even though
it is mostly zeroes.
Controlling constant initialization of globals#
Pigweed offers two utilities for declaring global variables:
pw::NoDestructor
– Removes the destructor, which is not necessary for globals. Constant initialization is supported, but not required.pw::RuntimeInitGlobal
– Removes the destructor. Prevents constant initialization.
It is recommended to specify constant or runtime initialization for all global variables.
Initialization |
Mutability |
Declaration |
---|---|---|
constant |
mutable |
constinit T constinit pw::NoDestructor<T> |
constant |
constant |
|
runtime |
mutable |
|
runtime |
constant |
|
unspecified |
constant |
const T const pw::NoDestructor<T> |
unspecified |
mutable |
T pw::NoDestructor<T> |
API reference#
pw_toolchain/constexpr_tag.h#
-
struct ConstexprTag#
Tag type used to differentiate between
constexpr
and non-constexpr
constructors. Do NOT use this feature for new classes! It should only be used to add aconstexpr
constructor to an existing class in limited circumstances.Specifically, some compilers are more likely to constant initialize global variables that have
constexpr
constructors. For large non-zero objects, this can increase binary size compared to runtime initialization. Non-zero constant initialized globals are typically placed in.data
or.rodata
instead of.bss
.Adding
constexpr
to a constructor may affect existing users if their compiler constant initializes globals that were runtime initialized previously. To maintain previous behavior, add a newconstexpr
constructor withConstexprTag
instead of changing the existing constructor.Prefer using
pw::kConstexpr
to select aconstexpr
-tagged constructor, rather than initializing apw::ConstexprTag
.Warning
Do NOT rely on whether a constructor is
constexpr
or not to control whether global variables are constant initialized. To control constant initialization, explicitly annotate global variables asconstinit
/PW_CONSTINIT
,constexpr
, orpw::RuntimeInitGlobal
. Compilers can constant initialize globals that:are not declared
constinit
orconstexpr
,do not have a
constexpr
constructor,or perform non-
constexpr
actions during construction, such as calling non-constexpr
functions or placement new.
-
constexpr ConstexprTag pw::kConstexpr = {}#
Value used to select a
constexpr
constructor tagged withpw::ConstexprTag
.
pw_toolchain/globals.h#
-
template<typename T>
class RuntimeInitGlobal# Declares a global variable that is initialized at runtime and whose destructor is never run.
This class is the same as
pw::NoDestructor
, except thatpw::NoDestructor
may beconstinit
ifT
isconstexpr
constructible.pw::RuntimeInitGlobal
instances can never beconstinit
.pw::RuntimeInitGlobal
prevents constant initialization inserting empty volatile inline assembly.Note
RuntimeInitGlobal
should only be used whenT
should not be constant initialized; otherwise, usepw::NoDestructor
. Constant initialization moves objects from.bss
to.data
. This can increase binary size if the object is larger than the code that initializes it.Warning
Misuse of
RuntimeInitGlobal
can cause memory leaks and other problems.RuntimeInitGlobal
should only be used for global variables.
pw_toolchain/no_destructor.h#
-
template<typename T>
class NoDestructor# Helper type to create a global or function-local static variable of type
T
whenT
has a non-trivial destructor. Storing aT
in apw::NoDestructor<T>
will prevent~T()
from running, even when the variable goes out of scope.This class is useful when a variable has static storage duration but its type has a non-trivial destructor. Destructor ordering is not defined and can cause issues in multithreaded environments. Additionally, removing destructor calls can save code size.
Except in generic code, do not use
pw::NoDestructor<T>
with trivially destructible types. Use the type directly instead. If the variable can beconstexpr
, make itconstexpr
.pw::NoDestructor<T>
provides a similar API tostd::optional
. Use*
or->
to access the wrapped type.NoDestructor
instances can beconstinit
ifT
has aconstexpr
constructor. In C++20,NoDestructor
instances may beconstexpr
ifT
has aconstexpr
destructor.NoDestructor
is unnecessary for literal types.Example usage:
pw::sync::Mutex& GetMutex() { // Use NoDestructor to avoid running the mutex destructor when exit-time // destructors run. static const pw::NoDestructor<pw::sync::Mutex> global_mutex; return *global_mutex; }
In Clang,
pw::NoDestructor
can be replaced with the [[clang::no_destroy]] attribute.pw::NoDestructor<T>
is similar to Chromium’sbase::NoDestructor<T>
in src/base/no_destructor.h.Note
NoDestructor<T>
instances may be constant initialized, whether they areconstinit
or not. This may be undesirable for large objects, since moving them from.bss
to.data
increases binary size. To prevent this, usepw::RuntimeInitGlobal
, which prevents constant initialization and removes the destructor.Warning
Misuse of
NoDestructor
can cause memory leaks and other problems. Only skip destructors when you know it is safe to do so.
pw_toolchain/infinite_loop.h#
-
inline void pw::InfiniteLoop()#
Loops infinitely. Call as
pw_InfiniteLoop()
in C.Infinite loops without side effects are undefined behavior. Use
pw::InfiniteLoop
in place of an emptywhile (true) {}
orfor (;;) {}
.
builtins#
builtins are LLVM’s equivalent of libgcc, the compiler will insert calls to
these routines. Setting the dir_pw_third_party_builtins
gn var to your
compiler-rt/builtins checkout will enable building builtins from source instead
of relying on the shipped libgcc.