Building Picolibc

Picolibc is designed to be cross-compiled for embedded systems on a Linux host using GCC. There is some support for Clang, but that doesn't include the built-in multilib support. Picolibc uses the meson build system, which is a slightly quirky build system designed to replace autotools with a single language.

Picolibc requires meson version 0.50 or newer. If your operating system provides an older version, you can get the latest using pip. For example, on a Debian or Ubuntu system, you would do:

$ sudo apt install pip
$ pip install meson

On POSIX systems, meson uses the low-level ‘ninja’ build tool and currently requires at least ninja version 1.5. If your operating system doesn't provide at least this version, head over to ninja-build.org to find out how to download and install the latest bits.

Selecting build options

Use -D= on the meson command line to change from the default value. Many of these options set configuration values for the newlib code base and should match that configuration system. The defaults should be reasonable for small embedded systems.

General build options

These options control some general build configuration values.

OptionDefaultDescription
fast-strcmptrueAlways optimize strcmp for performance (to make Dhrystone happy)
have-alias-attributeautoCompiler supports alias attribute (default autodetected)
have-format-attributeautoCompiler supports format attribute (default autodetected)
multilibtrueBuild every multilib configuration supported by the compiler
multilib-listIf non-empty, the set of multilib configurations to compile for
native-testsfalseBuild tests against native libc (used to validate tests)
picolibtrueInclude picolib bits for tls and sbrk support
picocrttrueBuild crt0.o (C startup function)
specsdirautoWhere to install the .specs file (default is in the GCC directory)
sysroot-installfalseInstall in GCC sysroot location (requires sysroot in GCC)
testsfalseEnable tests
tinystdiotrueUse tiny stdio from avr libc

If you set specsdir to none, then picolibc.specs will not be installed at all.

Options applying to both legacy stdio and tinystdio

These options extend support in printf and scanf for additional types and formats.

OptionDefaultDescription
io-c99-formatstrueEnable C99 support in IO functions like printf/scanf
io-long-longfalseEnable long long type support in IO functions like printf/scanf.
				  These are always enabled for the tinystdio full printf/scanf modes,
				  this option adds them to the limited (float and integer) versions, as well
				  as to the original newlib stdio bits                                                 |

Options when using tinystdio bits

These options apply when tinystdio is enabled, which is the default. For stdin/stdout/stderr, the application will need to provide __iob, which is an array of three pointers to FILE structures (which can be a single shared FILE structure).

Note that while posix-io support is enabled by default, using it will require that the underlying system offer the required functions. POSIX console support offers a built-in __iob definition which uses the same POSIX I/O functions.

OptionDefaultDescription
atomic-ungetctrueMake getc/ungetc re-entrant using atomic operations
io-float-exacttrueProvide round-trip support in float/string conversions
io-long-longfalseInclude long-long support in integer-only printf function
posix-iotrueProvide fopen/fdopen using POSIX I/O (requires open, close, read, write, lseek)
posix-consolefalseUse POSIX I/O for stdin/stdout/stderr

Options when using legacy stdio bits

Normally, Picolibc is built with the small stdio library adapted from avrlibc (newlib-tinystdio=true). It still has the original newlib stdio bits and those still work (newlib-tinystdio=false), but depend on POSIX I/O functions from the underlying system, and perform many malloc calls at runtime. These options are relevant only in that configuration

OptionDefaultDescription
newlib-elix-level0Extends stdio API based on level
newlib-fseek-optimizationfalseEnable fseek optimization
newlib-fvwrite-in-streamiofalseEnable iov in streamio
newlib-global-stdio-streamsfalseEnable global stdio streams
newlib-io-floatfalseEnable printf/scanf family float support
newlib-io-pos-argsfalseEnable printf-family positional arg support
newlib-io-long-doublefalseEnable long double type support in IO functions printf/scanf
newlib-nano-formatted-iofalseUse nano version formatted IO
newlib-reent-smallfalseEnable small reentrant struct support
newlib-stdio64trueInclude 64-bit APIs
newlib-unbuf-stream-optfalseEnable unbuffered stream optimization in streamio
newlib-wide-orientfalseTurn off wide orientation in streamio

Internationalization options

These options control which character sets are supported by iconv.

OptionDefaultDescription
newlib-iconv-encodingsComma-separated list of iconv encodings to be built-in (default all supported)
newlib-iconv-from-encodingsComma-separated list of “from” iconv encodings to be built-in (default iconv-encodings)
newlib-iconv-to-encodingsComma-separated list of “to” iconv encodings to be built-in (default iconv-encodings)
newlib-iconv-external-ccsfalseUse file system to store iconv tables. Requires fopen. (default built-in to memory)

Thes options control how much Locale support is included in the library. By default, picolibc only supports the ‘C’ locale.

OptionDefaultDescription
newlib-locale-infofalseEnable locale support
newlib-locale-info-extendedfalseEnable even more locale support
newlib-mbfalseEnable multibyte support

Startup/shutdown options

These control how much support picolibc includes for calling functions at startup and shutdown times.

OptionDefaultDescription
lite-exittrueEnable light weight exit
newlib-atexit-dynamic-allocfalseEnable dynamic allocation of atexit entries
newlib-global-atexitfalseEnable atexit data structure as global
newlib-initfinitrueSupport _init() and _fini() functions in picocrt
newlib-initfini-arraytrueUse .init_array and .fini_array sections in picocrt
newlib-register-finifalseEnable finalization function registration using atexit

Thread local storage support

By default, Picolibc can uses native TLS support as provided by the compiler, this allows re-entrancy into the library if the run-time environment supports that. As a separate option, you can make errno not use TLS if necessary.

OptionDefaultDescription
thread-local-storageautoUse TLS for global variables. Default is automatic based on compiler support
tls-modellocal-execSelect TLS model (global-dynamic, local-dynamic, initial-exec or local-exec)
newlib-global-errnofalseUse single global errno even when thread-local-storage=true

Malloc option

Picolibc offers two malloc implementations, the larger version offers better performance on large memory systems and for applications doing a lot of variable-sized allocations and deallocations. The smaller, default, implementation works best when applications perform few, persistent allocations.

OptionDefaultDescription
newlib-nano-malloctrueUse small-footprint nano-malloc implementation

Locking support

There are some functions in picolibc that use global data that needs protecting when accessed by multiple threads. The largest set of these are the legacy stdio code, but there are other functions that can use locking, e.g. when newlib-global-atexit is enabled, calls to atexit need to lock the shared global data structure if they may be called from multiple threads at the same time. By default, all of this is disabled as it would require underlying system support.

OptionDefaultDescription
newlib-retargetable-lockingfalseAllow locking routines to be retargeted at link time
newlib-multithreadfalseEnable support for multiple threads

Legacy newlib options

These either have no effect or should not be enabled in normal use of picolibc, they're left in the library to help users porting from newlib environments.

OptionDefaultDescription
newlib-long-time_tfalseDefine time_t to long instead of using a 64-bit type
newlib-supplied-syscallsfalseEnable newlib supplied syscalls (obsolete)
newlib-reentrant-syscalls-providedfalseUnderlying system provides reentrant syscall API
newlib-missing-syscall-namesfalseUnderlying system provides syscall names without leading underscore

Math library options

There are two versions of many libm functions, old ones from SunPro and new ones from ARM. The new ones are usually better for current hardware, except that the float-valued functions use double-precision computations. On systems with HW double support, that‘s likely a good choice. On sytems without HW double support, that’s going to pull in SW double code.

POSIX requires many of the math functions to set errno when exceptions occur; disabling that makes them only support fenv() exception reporting.

OptionDefaultDescription
newlib-obsolete-mathautoUse old code for both float and double valued functions
newlib-obsolete-math-floatautoUse old code for float-valued functions
newlib-obsolete-math-doubleautoUse old code for double-valued functions
want-math-errnofalseSet errno when exceptions occur

Building for embedded RISC-V and ARM systems

Meson sticks all of the cross-compilation build configuration bits in a separate configuration file. There are a bunch of things you need to set, which the build system really shouldn't care about. Example configuration settings for RISC-V processors are in cross-riscv64-unknown-elf.txt:

[binaries]
c = 'riscv64-unknown-elf-gcc'
ar = 'riscv64-unknown-elf-ar'
as = 'riscv64-unknown-elf-as'
ld = 'riscv64-unknown-elf-ld'
strip = 'riscv64-unknown-elf-strip'

[host_machine]
system = 'unknown'
cpu_family = 'riscv'
cpu = 'riscv'
endian = 'little'

[properties]
c_args = [ '-nostdlib', '-msave-restore', '-fno-common' ]
# default multilib is 64 bit
c_args_ = [ '-mcmodel=medany' ]
needs_exe_wrapper = true
skip_sanity_check = true

Settings for ARM processors are in cross-arm-none-eabi.txt:

[binaries]
c = 'arm-none-eabi-gcc'
ar = 'arm-none-eabi-ar'
as = 'arm-none-eabi-as'
ld = 'arm-none-eabi-ld'
strip = 'arm-none-eabi-strip'

[host_machine]
system = 'none'
cpu_family = 'arm'
cpu = 'arm'
endian = 'little'

[properties]
c_args = [ '-nostdlib', '-fno-common' ]
needs_exe_wrapper = true
skip_sanity_check = true

If those programs aren't in your path, you can edit the file to point wherever they may be.

Auto-detecting the compiler multi-lib configurations

The PicoLibc configuration detects the processor configurations supported by the compiler using the --print-multi-lib command-line option:

$ riscv64-unknown-elf-gcc --print-multi-lib
.;
rv32e/ilp32e;@march=rv32e@mabi=ilp32e
rv32ea/ilp32e;@march=rv32ea@mabi=ilp32e
rv32em/ilp32e;@march=rv32em@mabi=ilp32e
rv32eac/ilp32e;@march=rv32eac@mabi=ilp32e
rv32emac/ilp32e;@march=rv32emac@mabi=ilp32e
rv32i/ilp32;@march=rv32i@mabi=ilp32
rv32if/ilp32f;@march=rv32if@mabi=ilp32f
rv32ifd/ilp32d;@march=rv32ifd@mabi=ilp32d
rv32ia/ilp32;@march=rv32ia@mabi=ilp32
rv32iaf/ilp32f;@march=rv32iaf@mabi=ilp32f
rv32imaf/ilp32f;@march=rv32imaf@mabi=ilp32f
rv32iafd/ilp32d;@march=rv32iafd@mabi=ilp32d
rv32im/ilp32;@march=rv32im@mabi=ilp32
rv32imf/ilp32f;@march=rv32imf@mabi=ilp32f
rv32imfc/ilp32f;@march=rv32imfc@mabi=ilp32f
rv32imfd/ilp32d;@march=rv32imfd@mabi=ilp32d
rv32iac/ilp32;@march=rv32iac@mabi=ilp32
rv32imac/ilp32;@march=rv32imac@mabi=ilp32
rv32imafc/ilp32f;@march=rv32imafc@mabi=ilp32f
rv32imafdc/ilp32d;@march=rv32imafdc@mabi=ilp32d
rv64i/lp64;@march=rv64i@mabi=lp64
rv64if/lp64f;@march=rv64if@mabi=lp64f
rv64ifd/lp64d;@march=rv64ifd@mabi=lp64d
rv64ia/lp64;@march=rv64ia@mabi=lp64
rv64iaf/lp64f;@march=rv64iaf@mabi=lp64f
rv64imaf/lp64f;@march=rv64imaf@mabi=lp64f
rv64iafd/lp64d;@march=rv64iafd@mabi=lp64d
rv64im/lp64;@march=rv64im@mabi=lp64
rv64imf/lp64f;@march=rv64imf@mabi=lp64f
rv64imfc/lp64f;@march=rv64imfc@mabi=lp64f
rv64imfd/lp64d;@march=rv64imfd@mabi=lp64d
rv64iac/lp64;@march=rv64iac@mabi=lp64
rv64imac/lp64;@march=rv64imac@mabi=lp64
rv64imafc/lp64f;@march=rv64imafc@mabi=lp64f
rv64imafdc/lp64d;@march=rv64imafdc@mabi=lp64d

$ arm-none-eabi-gcc --print-multi-lib
.;
thumb;@mthumb
hard;@mfloat-abi=hard
thumb/v6-m;@mthumb@march=armv6s-m
thumb/v7-m;@mthumb@march=armv7-m
thumb/v7e-m;@mthumb@march=armv7e-m
thumb/v7-ar;@mthumb@march=armv7
thumb/v8-m.base;@mthumb@march=armv8-m.base
thumb/v8-m.main;@mthumb@march=armv8-m.main
thumb/v7e-m/fpv4-sp/softfp;@mthumb@march=armv7e-m@mfpu=fpv4-sp-d16@mfloat-abi=softfp
thumb/v7e-m/fpv4-sp/hard;@mthumb@march=armv7e-m@mfpu=fpv4-sp-d16@mfloat-abi=hard
thumb/v7e-m/fpv5/softfp;@mthumb@march=armv7e-m@mfpu=fpv5-d16@mfloat-abi=softfp
thumb/v7e-m/fpv5/hard;@mthumb@march=armv7e-m@mfpu=fpv5-d16@mfloat-abi=hard
thumb/v7-ar/fpv3/softfp;@mthumb@march=armv7@mfpu=vfpv3-d16@mfloat-abi=softfp
thumb/v7-ar/fpv3/hard;@mthumb@march=armv7@mfpu=vfpv3-d16@mfloat-abi=hard
thumb/v7-ar/fpv3/hard/be;@mthumb@march=armv7@mfpu=vfpv3-d16@mfloat-abi=hard@mbig-endian
thumb/v8-m.main/fpv5-sp/softfp;@mthumb@march=armv8-m.main@mfpu=fpv5-sp-d16@mfloat-abi=softfp
thumb/v8-m.main/fpv5-sp/hard;@mthumb@march=armv8-m.main@mfpu=fpv5-sp-d16@mfloat-abi=hard
thumb/v8-m.main/fpv5/softfp;@mthumb@march=armv8-m.main@mfpu=fpv5-d16@mfloat-abi=softfp
thumb/v8-m.main/fpv5/hard;@mthumb@march=armv8-m.main@mfpu=fpv5-d16@mfloat-abi=hard

On RISC-V, PicoLibc is compiled 36 times, while on ARM, the library is compiled 20 times with the specified compiler options (replace the '@'s with ‘-’ to see what they will be).

Running meson

Because Picolibc targets smaller systems like the SiFive FE310 or ARM Cortex-M0 parts with only a few kB of RAM and flash, the default values for all of the configuration options are designed to minimize the library code size. Here's the do-riscv-configure script from the repository that configures the library for small RISC-V systems:

#!/bin/sh
ARCH=riscv64-unknown-elf
DIR=`dirname $0`
meson "$DIR" \
    -Dincludedir=picolibc/$ARCH/include \
    -Dlibdir=picolibc/$ARCH/lib \
    --cross-file "$DIR"/cross-$ARCH.txt \
    "$@"

This script is designed to be run from a build directory, so you'd do:

$ mkdir build-riscv64-unknown-elf
$ cd build-riscv64-unknown-elf
$ ../do-riscv-configure

Compiling

Once configured, you can compile the libraries with

$ ninja
...
$ ninja install
...
$