|
|
Subscribe / Log in / New account

Zig heading toward a self-hosting compiler

Zig heading toward a self-hosting compiler

Posted Oct 8, 2020 19:23 UTC (Thu) by excors (subscriber, #95769)
In reply to: Zig heading toward a self-hosting compiler by roc
Parent article: Zig heading toward a self-hosting compiler

For embedded-style development, there is a third option beyond handling every individual allocation failure or restarting the whole application/OS on any allocation failure: don't do dynamic allocation. There's simple things like replace std::vector<T> with std::array<T, UPPER_BOUND> if you can work out the bounds statically. Or whenever an API function allocates an object, change it to just initialise an object that has been allocated by the caller. That caller can get the memory from its own caller (recursively), or from a static global variable, or from its stack (which is sort of dynamic but it's not too hard to be confident you won't run out of stack space), or from a memory pool when you can statically determine the maximum number of objects needed, or in rare cases it can allocate dynamically from a shared heap and be very careful about OOM handling.

E.g. FreeRTOS can be used with partly or entirely static allocation (https://www.freertos.org/Static_Vs_Dynamic_Memory_Allocat...). Your application can implement a new thread as a struct/class that contains an array for the stack, a StaticTask_t, and a bunch of queues and timers and mutexes and whatever. You pass the memory into FreeRTOS APIs which connect it to other threads with linked lists, so FreeRTOS doesn't do any allocation itself but doesn't impose any hardcoded bounds. And since you know your application will only have one instance of that thread, it can be statically allocated and the linker will guarantee there's enough RAM for it.

In terms of the application's call graph, you want to move the allocations (and therefore the possibility of allocation failure) as far away from the leaf functions as possible. Just do a few big allocations at a high level where it's easier to unwind. Leaf functions include the OS and the language's standard library and logging functions etc, so you really need them to be designed to not do dynamic allocation themselves, otherwise you have no hope of making this work.

The C++ standard library is bad at that, but the language gives you reasonable tools to implement your own statically-allocated containers (in particular using templates for parameterised sizes; it's much more painful in C without templates). From an extremely brief look at Zig, it appears to have similar tools (generics with compile-time sizes) and at least some of the standard library is designed to work with memory passed in by the caller (and the rest lets the caller provide the dynamic allocator). Rust presumably has similar tools, but I get the impression a lot of the standard library relies on a global allocator and has little interest in providing non-allocating APIs.

It's not always easy to write allocation-free code, and it's not always the most memory-efficient (because if your program uses objects A and B at non-overlapping times, it'll statically allocate A+B instead of dynamically allocating max(A,B)), but sometimes it is feasible and it's really nice to have the guarantee that you will never have to debug an out-of-memory crash. And even if you can't do it for the whole application, you still get some benefit from making large parts of it allocation-free.

(This is for code that's a long way below "complex applications" from a typical Linux developer's perspective. But nowadays there's still a load of development for e.g. IoT devices where memory is limited to KBs or single-digit MBs, implementing complicated protocols across a potentially hostile network, so it's a niche where a language that's nicer and safer than C/C++ but no less efficient would be very useful.)


to post comments

Zig heading toward a self-hosting compiler

Posted Oct 8, 2020 22:33 UTC (Thu) by roc (subscriber, #30627) [Link]

Yes, not allocating at all is a good option for some applications --- probably a lot more applications than those for which "check every allocation" is suitable.

There is a growing ecosystem of no-allocation Rust libraries, and Rust libraries that can optionally be configured to not allocate. These are "no-std" (but still use "core", which doesn't allocate). https://lib.rs/no-std

Rust const generics (getting closer!) will make static-allocation code easier to write in Rust.


Copyright © 2025, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds