 Hello, my name is Keith Packard. I work in the device OS group at Amazon as a senior principal engineer. Today I'm going to talk about Pico Lib C, a C library designed for embedded 32 and 64-bit microcontrollers. In this presentation, I'll describe what I see as the requirements for an embedded C library, the pieces from which Pico Lib C was built, how development progressed, results of that development along with several projects that have Pico Lib C support integrated into them. Finally, I'll briefly touch on future work that I hope to see done in Pico Lib C. Small systems are small. They can be really small. The embedded hardware iFly uses systems with 32 to 128 kilobytes of RAM and 4 to 32 kilobytes of RAM. They often use low-performance 32-bit cores. Some have a 32-bit floating-point unit, but many have no FPU at all. Developers writing long-running embedded software often want to avoid depending upon dynamic allegations exceeding. As it's easiest to prove that malloc will never fail if malloc is not linked into the application. As a developer, one of the steepest learning curves with embedded development is getting the very first Hello World application compiled linked and running on the target device. Arduino has had tremendous success by making that process easy enough for even me to teach to middle school students. Embedded systems generally make modest demands on the C library. The most commonly used functions are string and memory APIs, things like stir copy, men move, and the like. Applications doing computation may need some math functions like square root sign or the occasional Bessel function. Standard IO will often only be used during debugging to send output to a serial console to help diagnose software and hardware issues. However, applications may also use these functions to handle JSON, YAML, or other text interchange formats, and so strict standards conformance can be critical. While I am a strong proponent of share like licenses, a C library needs a pragmatic approach to be useful to all developers. A clear MIT or BSD license is best practiced in this space. To make starting with a new embedded platform easier, developers will benefit from extra support in the C library. First, the processor, memory, and library need to be initialized. Second, the linker needs to know how to lay out the application in RAM and RAM. This includes hardware-specific information, processor setup instructions, and library initialization code. Finally, providing debug output from the application using semi-hosting or other architecture-specific mechanism lets the developer see debugging information to four clocks when serial port drivers are working. With these three tools, a new SoC can be brought up using only the memory layout of the device, as we'll see later in some examples. When I started doing embedded development in the modern era in 2009, I was using an 8051 SoC. The compiler, SDCC, came with a small C library, so I didn't have to think about it. In 2012, when we built our first Cortex-M3 board, I also had to find a new C library. For this project, NewLib was too big and required too many support routines in the form of libgloss, including S-break from ALEC, which was required for standard IO. I initially settled on PDC lib, as that was at least reasonably small. However, it used a lot of stack space for printf and didn't have a math library. This library seems to be an experiment building a small C library, but not in building a C library for small systems. Some SoC vendors provide proprietary C libraries. These are often ports from older 8 and 16-bit systems, which can lack compliance with modern C standards in some areas. And of course, they're not free software, which makes them uninteresting to me. After a couple of years of using PDC lib, I decided to return to NewLib and see what can be done to make it usable for my work. I want to say first that NewLib is a great project. The code is easy to work on, the community is welcoming and supportive, and I've been happy to collaborate with them. Everything here relates to using NewLib for my small embedded projects, which is not what NewLib is really designed for. In looking at the changes that I wanted to make, the first thing on my list was StandardIO. NewLib StandardIO is designed to offer good performance and full compatibility and systems with an underlying POSIX API. It has internal buffering and allocates space on the heap for them. Replacing the whole thing seemed like the best option. I also wanted to get rid of the libgloss OS shim layer. Libgloss was originally designed as an interface between NewLib and whatever underlying operating system it was run on. With bare metal environments, there is no operating system, and so there really isn't a reason to have this library anymore. NewLib uses POSIX interfaces internally, porting to an RTOS is usually a simple matter of making sure the necessary POSIX interfaces are available. Fortunately, most of the common uses for libgloss APIs were in StandardIO. By replacing that, the bulk of the problem was already solved. One other huge consumer of RAM in NewLib is the struct re-ent. This structure holds all per thread state for the entire library. Replacing this with modern thread local storage techniques would free up a lot of memory while still allowing applications to take advantage of library APIs which needed persistent state. NewLib is an old code base that has seen development extended back to the pre-ANSI era of compilers. The C language has changed a lot since then, and how we use the language has changed even more. It's been a long time since I defined a new API with a bare int type. We now know that it's best to use a more specific type that won't surprise you with overflow issues or sign errors on some new platform. At a minimum, the library should compile cleanly with WLW extra flags. Finally, it's unreasonable to expect code review alone to catch errors and changes to the library. An extensive and automated test suite is table stakes for a project of this size. I got started hacking on the StandardIO changes that I wanted to make and sent them into the NewLib bailing us to see what feedback I might get. That was when the reality of what NewLib was hit me. NewLib is not a library for embedded application. NewLib is the SIGWIN C library which happens to be used by many embedded developers lacking an alternative. In particular, radical changes to the API and ABI of the library could never be integrated, as those would break binary compatibility for SIGWIN applications. So I decided I had to fork NewLib and start a new project. Once I decided to abandon attempts to upstream my work, I felt free to make all the changes I wanted, building the libraries I thought best. The good parts of NewLib I kept, the math, string, and locale implementations worked great. Having had experience with AVR Lipsy on app mail processors, I took that code as the basis for a new StandardIO implementation. To get rid of struct re-ent, I decided to replace it with thread local storage as supported by the C language. That involved changing nearly every file in the library, removing the parameter from internal functions, creating new thread local versions of all global data. The tests needed to run on target architectures. PicoLipsy includes enough infrastructure to build and run applications on bare metal hardware without depending on any external software. That includes startup code, linker scripts, and a mechanism to get error codes out of the test and up into the test framework running on Linux host. These are also what developers need when starting to work with a new chip. Changes to move the code base from the old style C to more modern practice were made so that PicoLipsy now builds with a C18 compiler and builds cleanly with GCC and Clang's WALW extra flags. NewLib also uses an old style build system with a maze of auto-tool bits and nested configure scripts. Instead of modifying those, I decided to overlay a new build system on top using Maison. This had a couple of advantages. First, it meant that I could ignore the existing build system so that upstream changes to that wouldn't affect merging my code changes in. Second, Maison builds are dramatically faster. A full build for all embedded ARM targets, compiling 28,429 files, takes only 38 seconds with an empty compiler cache. With a cache hot, that same build takes but 6.3 seconds. For some internal testing, I reanimated the NewLib test suite and then started adding more tests. NewLib includes piles of GPL code, but none of that is likely to be used in a non-SigWin binary. To make the PicoLipsy license status clear, all of the non-BSD license library code has been removed. The printf test cases came from a GPL license testing project, but those are not part of any possible library build. Now I'll start going into some of these changes in more detail, starting with the math code. One of the most useful bits of NewLib is the math library. That includes implementations of the standard math functions as specified in IEEE 754 and CC in POSIX. Lots of this code came from Sun back from the early 1990s. A couple of years ago, ARM provided some new float code using double precision intermediates to reduce the number of instructions necessary for these operations. Unfortunately, the resulting code is less accurate than the old Sun code. And of course, it's really only useful in hardware that has 64-bit floats. As a result, PicoLipsy disables this code into vault builds. Some of the math library codes split into two layers, a lower-level IEEE 754 API, which does the computation, including raising exceptions on hardware that has them, and a higher-level C API, which uses those lower-level functions and also updates the Erano as appropriate. The library can be built without Erano support, which eliminates the C wrappers and exposes the IEEE functions using the C names. These functions should be squashed together, moving the Erano bits into the lower-level functions. NCC has slowly refined the use of Erano and now has relatively consistent behavior based on the underlying IEEE 754 exception definitions. POSIX constrains behavior further. For instance, C17 says that a domain error may occur for POW 0,0, but provides no hint to the value returned, while POSIX requires the return value to be 1 and also requires that no errors are raised. PicoLipsy code has been modified to follow the POSIX spec as closely as practical. Exception generation relies on executing instructions that raise the appropriate exception, and that is challenging when C compilers work hard to move computation from runtime to compile time. I don't oversurefy a way to force a particular computation to happen at runtime. We've adopted some techniques which work with existing compilers, but we'll need to keep testing the library to make sure compilers don't outsmart us in the future. With the PicoLipsy testing infrastructure in place, tests were added to verify Erano and exception values for all math library functions and all exception causes. PicoLipsy inherits its standard I.O. from AVRLipsy. This code uses a small in-ram struct to hold per file state. PicoLipsy's standard I.O. is built on two primitive operations, get a character, and put a character. Any file struct includes pointers to functions which do just that. This means that implementing I.O. for another system involves implementing just these two functions, not a large set of POSIX APIs. To make PicoLipsy's standard I.O. code re-entrant without requiring locking, the un-get field is manipulated with atomic operations. As this field gets modified at runtime, the struct itself needs to live in RAM. There are macros which help applications initialize the structure correctly, also inherited from AVRLipsy. Standard in, standard out, and standard error are global pointers to file structs. They are declared const as PicoLipsy doesn't modify them, and so that they may be placed in RAM. These can all point to the same underlying file object if desired. For systems that do support POSIX functions, PicoLipsy provides F open in friends using those. FD open creates a file struct with getC and putC pointing at functions inside PicoLipsy, which then use POSIX, read and write. This support doesn't extend to fseq or rewind currently. Those are not yet implemented, although they could be added fairly easily. The standard I.O. code has made some obvious trade-offs for size and simplicity versus performance. Funneling all I.O. through narrow one byte functions slows things down. There are also no multi-byte or wide character support. For my applications, those have seemed like good choices. I'd like to hear what other people think. Print F, and to a lesser degree scan F, are key functions for any C library. When used for debugging, they need to be small and avoid interfering with other parts of the system. When used for data interchange, they need to be NCC and POSIX compliant. The original AVRLipsy print F is remarkably tight and has only a few issues with standards conformance. The one major gap is in printing and scanning floating point numbers. These are fundamentally base conversion functions converting between the base 10 printed form and the base 2 internal form. Think about how 1 tenth is represented in binary form as an infinite repeating fraction. This makes exact conversion impossible. So I'll redefine exact float spring conversion as a conversion where a number printed and then re-scanned generates exactly the same internal bits. Newlib, glibc and other C libraries use arbitrary precision numbers for this style of exact conversion. This is slow, large and generally requires malloc. PicoLibc uses the Rio fix precision algorithms developed by Ulf Adams to perform these operations. These require 128 bit operations for double and 64 bit operations for float conversions. This reduces code size and avoids malloc calls. PicoLibc has numerous additional fixes and improvements including hex format floats so that it nearly meets C18 requirements. The notable exception being numbered arguments. Newlib was developed before threading was common in POSIX environments. In that era, there was no C-language support for thread local storage. To work around this, Newlib introduced struct-rient. This struct contains every variable in the library which needs to be thread specific according to POSIX. That includes common things like air-know, but also rarely used items like sign-gam. The global variable used to hold the sign return from L-gamma. This struct uses 656 bytes on ARM32 bit. Newlib Nano includes changes which dynamically allocate much of the struct as needed reducing the static size to only 96 bytes. But PicoLibc eliminates this struct entirely instead using the C-language thread storage class. The linker gathers all of the variables of this class effectively creating a struct on the fly. Only TLS variables used by the application end up in the application. At runtime, access to these values uses an ABI-specific mechanism. For some architectures, PicoLibc supplies helper functions for this. PicoLibc also provides functions to allocate and initialize and set up ABI-specific mechanisms so that applications can manage multiple TLS blocks without needing a detailed understanding of the target architecture. We'll see this later in some examples. I was a bit surprised when I wrote my first Cortex-M3 code that I actually needed to initialize the processor, fill in the interrupt vectors, and initialize memory before starting my application. On 8 and 16 bit processors, this code is usually provided for you along with the compiler and C library. Like so many others, I wrote an application-specific version of the necessary code and compiled that with my application. For PicoLibc, I decided to take that and adapt it to be a bit more general, adding a default interrupt vector, initializing an heat-floating point unit, as well as setting up TLS. And now it could be used on most Cortex-M processors. After that, I wrote the equivalent code for RISC-5 and AR-64, then discovered that there are non-thumb arm processors which require completely different initialization code, so I added that as well. Finally, my friend Mike Hartel wrote startup code for x86 CPUs, both i686 as well as AMD64 mode. I bet very few of us have ever considered x86 as a bare-metal processor. As usual for embedded programming, these need to be customized, depending upon the target environment. So in PicoLibc, there are built three ways, depending upon the API support required. The normal way, which invokes constructors, but just spins if main returns. A hosted mode, which also calls exit, passing the return value from main. And a minimal mode, which doesn't even call the constructors. I use the hosted mode when running under QEMU to capture the exit status from test applications and report that to the test framework. But most of my actual applications use the normal variant, as I often have a constructor or two. Of course, some environments need even more control over startup and exit, and end up writing their own code. For them, the PicoLibc startup code can show what they need to do to support the library. I sometimes think that linker scripts are a dark art in embedded development world. The scripting language is a bit unusual, and the contents depend greatly on the implementation details of the C and C++ compilers, along with details about how the C library works. Gathering all these requirements together and writing a working script often takes many iterations and experiments. And even then, many of the scripts I've written only work by accident. For PicoLibc, I've tried to create a sample linker script that shows all the necessary pieces to build successful C and C++ applications, including handling thread local storage and C++ exceptions. The only information the developer needs is to provide the ROM and RAM addresses and sizes, and perhaps the desired size of a stack. For more sophisticated developers, the sample script should explain the language in PicoLibc requirements and help shorten the time to develop their own script. This script expects applications to put objects in special sections. For instance, the NVIC interrupt vector in Cortex-M processors usually needs to be the very first thing in Flash so that the reset vector and initial stack pointer can be loaded from it. The script also expects many symbols to be defined so that the code knows how memories are ranged. For instance, the startup code that sets up RAM needs to know where the contents of initialized data are in Flash and where they need to go in RAM. These are all defined in the PicoLibc documentation, but using the sample linker script can help clarify how to set things up. Semi-hosting allows a program running in a debug environment to communicate with the debugger. This can be used to provide debug output and status information even before the system clock and IO devices are running. Semi-hosting works with a hard-to-debug link like JTAG or from within QEMU. Using semi-hosting, the program places information and registers in memory and then invokes a breakpoint or other instruction which triggers a debugger visible event and halts the processor. The debugger can then use the register and memory contents to perform an operation, be that writing to the console or reading and writing data from the debugger host file system. ARM and RISC-V share the same spec which PicoLibc fully supports. x86 has a much smaller set of operations, limited to writing the console and terminating execution. Testing is key to the success of any system software like PicoLibc because most of the library will not be used by any one application. It's difficult to do in situ evaluation of the code. Instead, explicit low-level tests are needed to ensure the library conforms to the expected definitions. Fortunately, C libraries have several comprehensive specifications that define library behavior. This makes building a test suite a matter of identifying statements in the specifications that can be converted to assertions than writing code to test them. Newlib had an existing test suite which was not working when I found it. Fixing those tests and getting the code passing again took several months. The code fixes were shared with Newlib, but there was no way to fix the test suite in Newlib itself as it doesn't have any way to run tests in the target systems. To validate tests, they are all run against both PicoLibc and Glibc. PicoLibc contains numerous architecture-specific code paths, optimizing for space and speed using a combination of special C code and handwritten assembly. The target architectures have varying characteristics as well, from floating-point support to sizes of basic data types and even sign-in-ness of the char and w-char-t types. Making sure all this code operates correctly can only be done by executing on all the target architectures. PicoLibc does this by building bare-metal tests for each architecture and then running them under QEMU, which avoids needing a pile of sketchy embedded hardware for testing. Bare-metal code requires hardware-specific support in the form of startup code and linker scripts. Finally, the tests also need some way to report status back to the test harness, which uses the semi-hosting support in QEMU. QEMU includes support for numerous architectures, but not all of the supported CPU types are actually available. Every non-Cortex-M CPU can be used in the non-machine, but there is no equivalent for Cortex-M CPUs, which are only available in full SoC models. As a result, only the Cortex-M3 CPU can be tested in upstream QEMU. I submitted a patch adding a VRT-M machine that enabled testing of all Cortex-M models in QEMU, but that was rejected as out-of-scope for QEMU upstream. Within the upstream ULIB source are thousands and thousands of tests. Essentially, none of these work having been neglected for years. The math tests appear to have been translated a number of times, corrupting the expected values, places which should have been NAN or INF have been replaced with 64. Fortunately, the test code also contains paths to compute the expected values by calling the appropriate routines. I regenerated all the tables by using glibc as an oracle. New tests have been added to the test suite code as well in response to bug reports. With major rewrite of nano-malloc code, extensive malloc validation and stress testing was used to make sure it works in a wide range of environments. An external printf test suite was incorporated to make sure changes to printf continue to produce the expected results. Finally, a sequence of bug reports against NewLib's math routines, which reproduced incorrect exception and error-no results, led to the development of tests against all the M functions to make sure they produced the right error-no and exception values. To make sure the tests continue to work correctly, they are built against glibc and the output verified for that library as well as picolibc. With the tests reconstructed and a test-hardest built that uses QEMU to run them, all of the tests now pass on ARM, AR64, RISC-V, and X86 today. While the official upstream host for picolibc is at keithb.com, there is an active picolibc project at GitHub which accepts bug reports and merge requests. There are GitHub actions that test on each change to the main branch and all merge requests to main. This allows problems to be caught before being merged, offering confidence that the library continues to work as expected, even across fairly significant changes. GitHub appears happy to do a lot of testing for the project. Tests are built-in run on AR64, AMD64, ARM, RISC-V, and I686. Tests are also built on the extents at LX106. In addition, most of the tests can be run natively using the underlying system C library to provide POSIX functions. The actions build and run tests on both Linux and MacOS X systems using this mechanism. In total, picolibc is built 1,121 times, and the entire test suite is run 1,026 times. Some recent changes to picolibc include merging the math library into the C library because some of the C library functions wanted to use functions in the math library. This means that applications don't need to use dash lm to get access to the math functions anymore. A stub math library is included to provide backward compatibility. Printf and scanf recently gained support for hex format floats, %a in printf. The printf support is designed to produce the same output as glibc does, which is easy to understand for those of us familiar with IEEE 754 binary floating point formats. An ongoing source of trouble has been making C++ applications build with picolibc. Some of that has to do with how GCC sets up include paths and how libstdc++ headers are written. For cross tool ng, I've actually got a patch to GCC that removes the use of pound include next and header files as that would cause the compiler to find non picolibc headers following the libstdc++ headers. For the last week or so, I've been working to clean up the library so that it builds cleanly with the wextr compiler flag. Quite a bit of this work was caused by old style code which used inappropriate types like int for sizes or wintt instead of wcharity. This found a number of possible bugs but nothing that would likely cause problems in real use. Now it's time to show some samples of how picolibc can be used. The first one should be familiar to anyone who has learned to program in C. Hello.C is a classic first C program which can be used to verify a tool chain including the C library and the runtime environment because the string passed to printf is constant and ends with a new line. GCC optimizes this into a call to puts so the resulting binary doesn't contain printf at all. This linker script describes the memory layout of an STM32L152RBT6 Cortex M3SOC. It shows the 128 kilobytes of flash starting at 0x0800000 and the 16 kilobytes of RAM starting at 0x2000000. The picolibc linker script included at the end of this file takes those values and lays out the application and memory. Flash contains instructions, constant data and initialization values for RAM. At program start those initialization values are copied to RAM and the zero initialized area of RAM is cleared. If your application is fairly simple this style of linker script will probably work fine. If not you can use the picolibc linker script as a guide to what picolibc needs. It might be nice to include a collection of linker scripts like this one with definitions for common microcontrollers that would be one less step for starting a project. This command invokes GCC to compile the application and link it with picolibc. The dash dash spec sql picolibc.specs option loads a file adding command line parsing for picolibc options like the oslib and crt0 as well as telling the compiler how to find the right picolibc files. Dash dash oslib equals semi-host adds the picolibc semi-host library after the C library to connect picolibc to the semi-hosting environment. There's also a different library called dummyhost which provides dubs for tiny standard IO so that you can link an application using standard IO without any oslayer. The dash tstm.ld option uses the linker script that we showed above to arrange the program in memory. Here we see the amount of flash in RAM used by the sample application. Because printf has been replaced with puts by the compiler, the binary is surprisingly small. This is a complete Cortex M3 image that runs on real hardware and includes the interrupt vector and semi-hosting interface routines. This script loads the program in flash and then starts it running while monitoring the device for semi-hosting requests. Console output from the application appears on standard out. This example is unusually short because OpenOCD includes information about the STM32L discovery board which includes built-in debug connection which avoids needing to specify both SOC and debug dongle information. You can also run this application under Quemu that requires relinking using a different script as the memory addresses supported by the nps2-an385 Quemu model are different from the STM32L152RBT6 with flash starting at 0 instead of 0x08000 otherwise the .l file is identical. Quemu also supports semi-hosting which can be enabled on the command line. A script like this is how PicoLibSe runs all the tests during development. Here's a program that shows the size of PicoLibSe's printf function. Unlike the first example, this one includes an argument so that it isn't converted into a call to puts. We use a floating point number to show the size of the different versions of the printf provided in PicoLibSe. Using a bit of simple definition magic PicoLibSe lets you select which type of printf function your application will use at link time. Because of the way floating point numbers are passed to var arcs functions like printf we need to use printf float to handle the 32-bit float value. When using the float-only version of printf this inline function wraps the 32-bit float in a 32-bit int preventing it from being extended to a 64-bit double. When not using the float-only printf this function casts to a 64-bit double. This example can be compiled and run like the hello world example above. This table shows the different size of the variance. The first is the full version of printf supporting 32 and 64-bit floats. The second only provides support for 32-bit floats. The third has no float support and doesn't support 64-bit ints. To add 64-bit int support would increase the size by 400 bytes. I also included sizes for the original Lulub code both full float and integer only. The initial RAM size plus the amount of memory allocated from the heap at runtime. You can see from these numbers why many developers avoid printf in standard IO often writing custom IO routines. Adapting the AVR standard IO in printf code along with the Ryu floating point conversion code provides a standard IO that fits in most ROM budgets while using very little RAM. Here's one last example which is more like a typical embedded application. This program blinks the blue LEDs on an STM32L discovery board by accessing the GPIO controller on the SOC programming the two pins connected to the LEDs' outputs and then toggling them with a delay loop to slow things down enough to see. This example doesn't use much of the library functions, but it does use the startup code in Likerscripts so even the short example is easy to write with PicoLibC than without. Now I want to talk about work done in other projects including PicoLibC including tool chains, operating systems and hardware emulation. Lots of people are using C++ for embedded applications and they often want to use higher level libraries including libsteaded C++. One of the pieces of libsteaded C++ is IOStream which provides abstractions on top of regular C standard IO. The GCC libsteaded C++ implementation has performance optimizations which penetrate the standard IO interaction with custom code for both glibc and newlib directly manipulating private fields within the file struct and bypassing the C library with direct calls to the underlying POSIX API. I submitted patches to GCC which eliminated these optimizations to make libsteaded C++ work with PicoLibC. These are selected by building GCC with the enable standard IO equals standard IO underscore pure flag and they are now included in GCC version 11. The INRIA CompSert project is building a formally verified C compiler for embedded software. Projects using this compiler would prefer a C library also built with a verified compiler. CompSert developers have provided patches to PicoLibC that allow it to be built with their compiler. Most of these changes fix GCC in clang specific language constructs in the code and so they have generally improved the portability and C standard CrosToolNG is a cross compilation toolchain building system. Select a target system in a suite of libraries and CrosToolNG will download the bits, build the host tools, compilers and libraries creating a self-contained toolchain ready to build your embedded projects. Zephyr uses CrosToolNG to build its native toolchain. One of the assumptions built in a CrosToolNG is that there is only one C library. For most users that's pretty reasonable as they only need to use one C library to build their projects and that's how initially integrated PicoLibC into CrosToolNG offering another C library alternative to NewLib and NewLibNano. With a resulting toolchain I managed to build and test numerous applications including the Zephyr tests. However, that's not how Zephyr project was using CrosToolNG. Within the Zephyr fork of CrosToolNG was a Kluge patch to build both NewLib and NewLibNano. This allowed Zephyr users to download a single toolchain and decide while building the Zephyr project which C library used. I decided to work from there and create a mechanism that could be upstreamed in a CrosToolNG that would build all three C libraries PicoLibC, NewLib and NewLibNano and offer the users a mechanism to select between them while building an application with a toolchain. Working within the CrosToolNG community we ended up using the companion library mechanism for NewLibNano and PicoLibC to build these in addition to the core NewLibC library. You can also ask to build only one of the three although the configuration mechanism is a bit clunky. Taking advantage of the libstd at C++ patches incorporated into GCC, CrosToolNG also builds libstd at C++ for all three libraries. All this work is now in CrosToolNG. Riot is an operating system for IoT devices. There's a fun group of people working on it and they discovered that they were already working on integrating PicoLibC in the summer of 2020. I joined it and helped answer a couple of questions. That code got merged in August of 2020 and has seen a steady uptick in usage by Riot developers. Because of the straightforward Riot code base adding TLS support was easy as it could use the PicoLibC TLS helpers unchanged. The whole integration work took about 500 lines of new code in Riot including the TLS bits. Reviewing Riot development over the last year there have been quite a few patches related to PicoLibC so it seems pretty clear that PicoLibC has taken root in the Riot community. If you're looking for a great little IoT operating system I'd encourage you to look here. Many of you are probably familiar with Zephyr, the embedded operating system sponsored by the Linux Foundation. I got interested in when I discovered they were getting serious about memory protection stuff. An embedded operating system that took advantage of memory protection hardware available in smaller SOC devices sounds like a good thing to me. The PicoLibC integration work started about two years ago. I sent the initial pull request that included TLS support for 32-bit ARM processors. Zephyr has a lot more processor specific assembly code including much of the context switch code with the architecture specific TLS hooks needed to go. It was easier to just do that part in line. After submitting the pull request a couple of people who had been thinking about TLS support jumped in and made the rest of the TLS support part of Zephyr and that series got merged in. But the main PicoLibC integration hasn't been accepted yet. The initial blocker was a lack of tool chain support which led to getting PicoLibC integrated into CrosstoolNG which led to fixing libstandard C++ to support other libc implementations. By then I was off working on other things and unable to get back to Zephyr. I've got the patches re-based and working again. I'll try to see if I can get them posted and ask if somebody else wants to help bring this series home. PicoLibC could also replace the built-in minimal C library in the Zephyr tree. The PicoLibC code is smaller and faster in all cases while also including a math library and other parts of a standard C environment. This could also replace the cbprintf and printkpas in the system reducing ROM prints quite a bit while improving standards conformance. I started the PicoLibC project because I needed a solid C library for my own microcontroller projects. So I thought I should tell you about some of those projects. First is Altos which is a suite of firmware built into the products that BDL Garby and I sell for use in high power amateur rocketry. This firmware is also included in several amateur radio satellites currently in orbit. While we started with a venerable 8051 microcontroller many years ago we've almost completely migrated to the ARM Cortex-M processors ranging from the STM32 FO42 through the STM32 L151. We started looking at some Cortex-M4 processors or some applications requiring higher performance but haven't quite gotten there. If you're interested check out altosmetrum.org. Next we built Chaos Key which is a USB connected true random number generator built using the STM32 FO42 processor. We built a thousand of these and sold them to people around the world. For that same hardware platform I wrote a tiny scheme interpreter and called the result Lambda Key. It was largely a gag but it did create enough language infrastructure for my next project. That next project was SNEC a subset of Python that runs on a huge range of small microcontrollers from the ATMEGA 328P in the original Arduino to the SAMD 21 chips found in a range of Adafruit products. SNEC is also the primary firmware for SNEC board also based on the SAMD 21 processor. SNEC board has motor controllers and IO pins designed to work with LEGO Power Functions hardware. I ran a crowd supply campaign last year so that I could build a bunch of these boards and started using them in a middle school LEGO Robotics class just as the pandemic began. I'm hoping to get back to teaching kids as soon as I can. The PicoLibSea code has tried to remain reasonably source compatible with NewLib so that patches can be easily exchanged. For example, a bunch of bug fixes for exceptions and Arduino supported math functions have been passed from PicoLibSea back to NewLib. Some patches require editing to get them building in NewLib again. Without a comprehensive test suite it's difficult to know if they work. Changes from NewLib are added into PicoLibSea from time to time. Few of the patches are relevant as most NewLib work relates to SIGWIN support which has been removed from PicoLibSea. I'm happy to continue supporting the NewLib project. Much of the code for PicoLibSea had its origins there and giving back to a community that has continued to improve so much useful code for so many years seems like the best way to thank them. There are always things to do with an old body of code like PicoLibSea. Some of the functions are still declared using KNR syntax. Much of the older and less frequently used code like the search stuff is still written with a casual approach to typing leaving questions about integer overflow safety and the like. Similarly the math library is showing its age with numerous functions less accurate than many other C libraries like Muscle and G-LibSea. This is especially true for odd corners of the library like T-Gamma and the Bessel functions. PicoLibSea can't simply adopt the G-LibSea implementations giving licensing differences. I would also like to see integer only implementations of some of the more important math functions for use on soft float hardware. To help people starting with a new device it would be nice to include linker scripts and compiler options for a range of embedded SOC parts. That way you wouldn't have to dig through data sheets to discover where memory was mapped and what kind of core was provided. Finally we're starting to see more activity with people using PicoLibSea but it's still a pretty quiet project. And that's the end of my presentation. I hope you found it useful and want to thank you for taking time to listen today.