This library provides clean, ergonomic, and consistent APIs for low-level and machine-dependent code across a wide variety of low-level environments. It provides some machine-independent APIs abstracting some simple machine dependencies, but mostly provides APIs that are inherently machine-dependent.
lib/arch code is intended to be compatible with a wide variety of low-level environments. Not every API is usable in every context. But everywhere possible, every API is written to be compatible with all these different contexts. For example, due to the Multiboot and EFI environments, nothing uses
long int types and instead everything carefully uses
uintptr_t, etc. to distinguish specific bit widths from pointer and type sizes (pointers are 32 bits in Multiboot,
long int is 32 bits in EFI though pointers are 64 bits).
The main Zircon kernel is about the richest and most forgiving of the environments where
lib/arch code runs. Yet it has more constraints than userspace code.
This is another kernel-like environment that is even more constrained. It's used in some “bare metal”-like contexts that work with the kernel:
These places have many more things they can't do:
"string literal" for
const or data is best.
mwait-style features), let alone spawn threads or the like.
Multiboot is a legacy boot method used by some pre-EFI x86 boot loaders such as GRUB, and by QEMU's support for directly booting x86 kernels without a boot loader.
This is a
kernel.phys-like environment, but it's actually x86-32 (i686), i.e. ILP32!
The EFI environment is used to build the Gigaboot boot loader, or other tests and tools that run in EFI. This uses the Windows object file format (PE-COFF), and so code is compiled by what‘s essentially a Windows toolchain (e.g.
long int is 32 bits while pointers are 64 bits). It’s the same Clang C++ front end, but the calling convention, object file, and linking details are like Windows.
lib/arch is primarily geared towards the needs of privileged (kernel) code. But applicable pieces can also be used in userspace, e.g.
lib/arch code is largely standalone leaf functions without its own dependencies. But some other header and library dependencies are both acceptable and encouraged in
lib/arch should be entirely compatible with a full-fledged standard C library. But it should depend only on the most minimal “bare metal” subset of library APIs. In privileged environments,
lib/arch uses the Zircon kernel libc. This provides only these standard APIs:
printf family from
stderr but only those two (which might actually be the same one) exist and no others can be created
fprintf output to
stderr is probably not thread-safe or interrupt-safe, may busy-wait slowly for large output strings, etc.
static_assert whenever possible. All runtime assertions should use
<zircon/assert.h>. This is available (with various different implementations of
__zx_panic) in all the supported environments.
ktl subset of standard C++ library functionality can be used freely in
lib/arch and phys code. For an API that makes sense in userspace, the standard headers and
std:: names can be used directly instead of the
ktl:: wrappers--but care must be taken to stick to the subset that are exported with
ktl:: wrappers, as only those are approved for use in kernel code.
Other libraries can be used as long as they are compatible with the kernel's constraints. This is a non-exhaustive list:
lib/arch interfaces should be well-isolated, well-documented, clean APIs.
Hardware bit layouts are expressed using
hwreg types, never with ad hoc
#define or direct use of constants.
All header files are under
<lib/arch/...>. Some headers provide machine-independent APIs and some provide machine-specific APIs and some provide a mix of the two. In general a common header file name is used for the same API topic across machines even if the actual APIs are partly or wholly machine-specific. This reduces the number of places where the code using the library needs
#if tests for each machine. For example,
<lib/arch/intrin.h> provides many machine-specific APIs as well as a few machine-independent APIs but there aren't separate
<lib/arch/x86/intrin.h> names to
There is a source subdirectory for each machine, named by the kernel‘s name for the CPU (
$zircon_cpu in GN, i.e.
arm64 or not
x86). Each is actually a
library() target of its own, but users of
lib/arch don’t know the sub-library exists, they just use
deps = [ "$zx/kernel/lib/arch" ]. The machine-specific subdirectory provides
include/lib/arch/... files for the machine-dependent header files with machine-independent file names.
These subdirectories should be reserved for the things that really can't be compiled on a different machine, such as code using inline
asm or intrinsics. Declarations that are simply about only one machine should go into the main
include/lib/arch/... directory, possibly under a machine-specific subdirectory there. Things like
hwreg declarations for system registers are topically particular to one machine architecture, but the declarations themselves can and should be portable to any machine and indeed to any operating system. That makes it possible to use these headers in
hwreg::AsmHeader generator programs, which are compiled on the build host. Such declarations may also be useful in unit test code that can sometimes be built and tested on a different machine and/or operating system.
Only C++ 17 with modern style is supported. There is no provision for C or for C++ 14.
lib/arch C++ APIs use the
All public APIs are documented with clang-doc-style
/// comments before the declaration.
Assembly code is minimized, preferring to use compiler intrinsics or inline
__asm__ in C++ code whenever that's possible. Standalone assembly code is in
.S files with straightforward style using two-space indentation and C++-style
// comments, and uses
<lib/arch/asm.h> macros for symbol definitions.
Header files that are compatible with assembly use
#ifdef __ASSEMBLER__ to separate assembly-only and C++-only declarations. All header files are compatible with C++ even if they have nothing outside
Macros for assembly code have an assembly API flavor and are defined as GAS assembly macros using
.macro, not as C macros using
#define. Assembly macro APIs are documented using
/// comments before the
Public macros that do not generate instructions have names starting with
., such as
<lib/arch/asm.h>. Macros that generate instructions have instruction-like names with no particular prefix.
Internal macros not used outside a header file have names starting with
_ (and thus
_. for non-instruction-generating macros) and do not get
Isolated trivial integer constants used in both C++ and assembly can be defined in header files using
#define. However, most constants should be defined in C++ using
constexpr (often via
hwreg types). When assembly code needs to use those values, create a generated header file using the
hwreg::AsmHeader API and the
hwreg_asm_header() GN template.
TODO(mcgrathr) Describe testing methodology.