LLVM libc support
July 2, 2026 ยท View on GitHub
Arm Toolchain for Embedded (ATfE) uses
picolibc as the standard C
library. This will change in a future version to use the LLVM project's own
C library that is now provided with ATfE to facilitate
the migration.
Building the toolchain with LLVM libc
Enable LLVM libc with -DLLVM_TOOLCHAIN_ENABLE_LLVMLIBC=ON.
By default, ATfE builds with picolibc enabled. To build both picolibc
and LLVM libc, use:
cmake .. -GNinja -DLLVM_TOOLCHAIN_ENABLE_LLVMLIBC=ON
To build only LLVM libc, disable picolibc explicitly:
cmake .. -GNinja \
-DLLVM_TOOLCHAIN_ENABLE_PICOLIBC=OFF \
-DLLVM_TOOLCHAIN_ENABLE_LLVMLIBC=ON
Note
The legacy option -DLLVM_TOOLCHAIN_C_LIBRARY=llvmlibc is still accepted
for backwards compatibility, but it is deprecated.
If you also add -DLLVM_TOOLCHAIN_LIBRARY_OVERLAY_INSTALL=ON, then the
package-llvm-toolchain CMake target generates an LLVM libc overlay package.
Overlay builds require LLVM libc to be the only enabled C library.
Using LLVM libc
To compile a program with this LLVM libc, you must provide the
following command line options, in addition to --target, -march or
-mcpu, and the input and output files:
-
--config=llvmlibc.cfgif LLVM libc is installed as a secondary C library in the standard ATfE package wherepicolibcremains the primary library, or when using an LLVM libc overlay package. You do not need this option if you built the toolchain with only LLVM libc enabled. -
-nostartfilesto not include the currently non-existentcrt0.o -
-lcrt0-semihostto include a library defining the_startsymbol (or else provide that symbol yourself) -
-lsemihostto include a library that implements porting functions in LLVM's libc in terms of the Arm semihosting API (or else provide an alternative implementation of those functions yourself) -
-latomic-fallbackto include weak fallback definitions for compiler-emitted__atomic_*libcalls. This is mainly useful for targets such as Armv4T, Armv5TE, and Armv6-M where the compiler may emit calls to helper functions for atomic operations that are not lock-free. Do not use this option for targets whose atomic helpers are provided bycompiler-rt. See the shared support library documentation. -
-ldummypackeysoptionally, for M-profile PACBTI variants only, to install dummy PAC keys for testing and enable PAC/BTI in the default startup hook. Do not use this library in production builds - overridevoid _platform_setup_arch_extensions()instead, see below. -
-T llvmlibc.ldto include the default linker script. Alternatively, you can include the linker script in your custom linker script, similar to howpicolibc.ldis used, or write your own linker script defining__stack, and__llvm_libc_heap_limitif you are using the heap
Important
The default llvmlibc.ld is provided for testing and is derived from the
picolibc.ld licensed under the BSD 3 Clause license. This may cause
licensing obligations if used in real projects.
For example:
clang --config=llvmlibc.cfg --target=arm-none-eabi -march=armv7m -nostartfiles -lcrt0-semihost -lsemihost -T llvmlibc.ld -o hello hello.c
Tip
For easier migration from picolibc to LLVM libc, use the following startup libraries:
-lcrt0the default startup library that provides initialization and exit for not hosted environments. You can override one or more of the following functions in your application:void _platform_setup_exceptions()void _platform_setup_memory()void _platform_setup_arch_extensions()void _platform_init_data_segments()void _platform_init_tls()void _platform_init()void _platform_debug_putc(int c)int _platform_get_argv(char *cmdline, int max_cmdline, const char **argv, int max_argv)void __llvm_libc_exit(int status)
-lcrt0-semihoststartup library to be used with the semihosting library-lsemihost.-lcrt0-nonean empty library, you have to provide the_startsymbol.
Migrating from picolibc to LLVM libc
The C standard leaves some C library behavior implementation-defined, which might
impact your project during migration from picolibc to LLVM libc.
The following sections summarize some of the differences.
Zero-size allocation with malloc(0) and calloc(0, size)
Standard reference: C17 7.22.3.
The standard permits either a null pointer or a non-null pointer that must not
be used to access an object. picolibc rounds malloc(0) up to its minimum
allocation size and can return a non-null pointer. LLVM libc returns NULL for
zero-size allocation.
Zero-size reallocation with realloc(ptr, 0)
Standard reference: C17 7.22.3 and 7.22.3.5.
C17 permits implementation-defined zero-size allocation behavior. In the current
implementations, both picolibc and LLVM libc free ptr and return NULL.
Locale support
Standard reference: C17 7.11.1.1 and 7.11.2.1.
Locale names other than "C" are implementation-defined. picolibc supports
several locale names and character sets. LLVM libc in ATfE currently accepts
only the "C" locale and returns NULL for other locale names.
Signal support
Standard reference: C17 7.14 and 7.14.1.1.
The set of supported signals and parts of signal handler behavior are
implementation-defined. picolibc provides fallback signal state for bare-metal
and semihosted environments. The ATfE bare-metal entrypoint set for LLVM libc
does not currently include a comparable signal implementation.
Clock and calendar-time sources
Standard reference: C17 7.27.2.1, 7.27.2.4, and 7.27.2.5.
The availability, range, precision, and platform source of time values are
implementation choices. picolibc time functions are normally backed by OS or
semihosting hooks such as gettimeofday, times, or semihosting calls. LLVM
libc bare-metal clock and timespec_get call ATfE retargeting hooks.
Other implementation-defined choices
Other examples of standard-library implementation-defined choices that might
impact migration are the exact values of library macros, the supported set of
error numbers, the value used by EXIT_FAILURE, and the representation and
range of library typedefs such as size_t, wchar_t, time_t, and clock_t.
LLVM libc initialization
When used with ATfE-provided crt0 startup code, LLVM libc calls the following
functions in this order:
void _platform_setup_exceptions()void _platform_setup_memory()void _platform_setup_arch_extensions()void _platform_init_data_segments()void _platform_init_tls()void _platform_init()
You can override any of these functions in your application to customize. The expected behavior is as follows:
void _platform_setup_exceptions()- Set up the exceptions table and enable relevant interrupts.void _platform_setup_memory()- Set up the Memory Management Unit and caches.void _platform_setup_arch_extensions()- Set up architecture extensions that require special initialization, for example, security features that require a cryptographic key.void _platform_init_data_segments()- Relocate read-write data into its runtime memory and clear the BSS (uninitialized static storage) region. By default, the following linker script symbols are used:__data_source- the load address of the start of the read-write data image in ROM/flash.__data_start- the destination address of the start of the read-write segment.__data_size- the size of the read-write data segment to be copied.__bss_start- the address of the start of the BSS region.__bss_size- the size of the BSS region to be cleared.
void _platform_init_tls()- Initialize the initial thread-local storage block for the startup thread. By default, this uses the picolibc-compatible linker script symbols__tls_first,__tls_size,__arm32_tls_tcb_offset, and__arm64_tls_tcb_offset.void _platform_init()- Any other initialization right before the main function is called, for example, setup standard I/O streams.
Without ATfE provided crt0 startup code you have to handle the following in
your own startup code:
- Initialize all relevant aspects of the hardware.
- Copy read-write and zero-initialized data.
- Call
__libc_init_array()to run constructors. - Call the main function.
Note
For the Memory Management Unit setup LLVM libc startup code constructs the
initial translation table at runtime and therefore uses RAM (up to 4KB for
AArch64), in contrast to picolibc that provides the table as part of the ROM
image.
LLVM libc finalization
LLVM libc performs required clean up like calling destructors, then calls
void __llvm_libc_exit(int status) to finish execution in a way appropriate
for the platform.
The default ATfE provided crt0 handler is an infinite loop.
I/O retargeting
See the baremetal version of io.h for the LLVM libc I/O retargeting interface that should be implemented in your application to redirect standard I/O streams.
Example implementations are provided for:
- Semihosting: semihost.cpp
- UART output: samples.
Debug output
LLVM libc startup code uses void _platform_debug_putc(int c) to
emit debug diagnostic messages, for example, inside exception handlers.
With crt0-semihost the output is directed to stderr using semihosting.
With crt0 the output is discarded. You can implement
void _platform_debug_putc(int c) in your application to redirect it,
for example, to a UART. The implementation must be safe to be called with
only the assumption that the stack pointer is setup: it must not use heap or
other LLVM libc functions. It must not access any code or data that is
initialized by _platform_init_data_segments().
Providing command line options
crt0-semihost supports getting command line options via semihosting and
passing them as argc and argv to the main function.
Semihosting passes the options as one line parsed by crt0-semihost using the
following rules:
- Arguments are split by a whitespace.
- No special handling for the program name:
argv[0]will contain the first argument provided by semihosting. - To pass an argument with a space in it, use quotation marks, for example,
"a b c "or'a b c 'will keep all spaces. Alternatively, use\to escape the space, for example,a\ b\ c\. Note that if the closing quotation mark is missing then all text till the end of the provided command line will be treated as one argument. - To pass a quotation mark or backslash, use escape sequences:
\",\'and\\to put",'and\respectively. In general,\is treated as escape and copies the next character unless inside a single quotation'or at the end of the provided string then it does not have special meaning.
When using crt0 in a no-host environment, you can provide your own
implementation of
int _platform_get_argv(char *cmdline, int max_cmdline, const char **argv, int max_argv)
to provide argc and argv to the main function:
_platform_get_argvaccepts the following parameters:char *cmdline- the buffer to put the command line into.int max_cmdline- the size of thecmdlinebuffer.const char **argv- the buffer to put the arguments into ornullptrto request the estimation of the size required for this buffer.int max_argv- the size ofargvarray.
_platform_get_argvreturns the following:- Number of arguments in the command line string if the provided
argvisnullptr(max_argvis ignored in this case). Theargvarray must provide one extra space for the terminator. - The number of parsed arguments -
argc- if the providedargvis notnullptr. -1in case of an error.
- Number of arguments in the command line string if the provided
The maximum accepted command line length is 255 characters.
Samples
LLVM libc uses the same shared sample set as picolibc. Therefore, there is no
separate samples/llvmlibc directory. The samples are installed under
samples/src/.
To build any sample with LLVM libc, change to the sample directory and set the
LIBC environment variable to llvmlibc, for example:
$ LIBC=llvmlibc make build
Warning
C++ samples have limitations described below when used with LLVM libc.
Limitations of LLVM libc in Arm Toolchain for Embedded
LLVM libc is a production-quality implementation, but it currently provides only a subset of the functions defined by the C standard. Its coverage is sufficient for typical embedded use cases and continues to grow based on user needs and requests.
ATfE builds of the C++ libraries are limited to what is supported
in conjunction with LLVM libc. For example, file I/O and fstream are not
available - only console iostream can be used.