Hey all,
Put together a first pass at a porting guide for STM32 projects to Zig’s compiler:
I have grander aspirations of potentially adding a general “porting” section to zig.guide if there’s interest!
Hey all,
Put together a first pass at a porting guide for STM32 projects to Zig’s compiler:
I have grander aspirations of potentially adding a general “porting” section to zig.guide if there’s interest!
Thanks for putting this together. I just got a discovery board and wanted to start tinkering with it.
Nice tutorial @haydenridd !
syscall.c
: It is not needed when nosys
lib is includedsysmem.c
: I need to implement _sbrk
if nosys
lib is not includedI was able to enable float support by adding this line to my build.zig
config
elf.forceUndefinedSymbol("_printf_float"); // Allow float formatting (printf, sprintf, ...)
libnosys.a
, malloc will not work. Am I right ?libg_nano.a
but I see that is included by GCC in my case at linking stage. What is the purpose of this library ?Good callout, I was originally using the compiler version that comes “bundled” with the STM32CubeMX stuff, but I think I’ll update the tutorial to use the latest official release from ARM. Yup, essentially the sys**.c files provided by ST provide similar functionality to the “nosys” library. I actually ran into some interesting duplicate symbol errors using the Zig compiler because of this that wasn’t present in GCC. I believe zig cc/clang handles “weak” symbols differently than GCC which is a whole other nightmare I haven’t started digging into yet
Whoa… I’ll hand it to newlib that is a proper cursed way of enabling an option via the linker You may want to read up on this it looks like you may want to tread lightly using floats with newlib’s printf()
implementation.
Questions:
nosys
newlib ships with a functional malloc
implementation (and when you get down to the nitty gritty functional _sbrk
implementation) that depends on having linker symbols that specify where the heap starts/ends __heap_start__
and __heap_end__
). So “malloc” may or may not work correctly depending on if you have the linker symbols it wants. And to find those you’ll have to go to the newlib source code and see what it wants. I believe since ST doesn’t define these symbols in their generated linker script, but use their own (_estack
), you’ll want to stick with their implementation in sysmem.c.malloc
with this implementation as it is super simple and really is only suitable for allocating all your things at program start and never touching them after. Why dynamic allocation is tricky on MCU’s is a whole other topic, look up what a “Memory Management Unit” is and note that generally MCUs don’t have these.EDIT:
I went ahead and dug into the newlib source since I was curious. ST actually DOES provide the correct linker symbol, which in this case is end
. Here’s the snippet from newlib source that uses it:
oid *
_sbrk (incr)
int incr;
{
extern char end; /* Set by linker. */
static char * heap_end;
char * prev_heap_end;
if (heap_end == 0)
heap_end = & end;
prev_heap_end = heap_end;
heap_end += incr;
return (void *) prev_heap_end;
}
And the section in the linker script that provides it:
/* User_heap_stack section, used to check that there is enough RAM left */
._user_heap_stack (NOLOAD) :
{
. = ALIGN(8);
PROVIDE ( end = . );
PROVIDE ( _end = . );
. = . + _Min_Heap_Size;`
. = . + _Min_Stack_Size;
. = ALIGN(8);
} >RAM
So malloc will work out of the box even without ST’s sysmem.c
.
I believe zig cc/clang handles “weak” symbols differently than GCC which is a whole other nightmare I haven’t started digging into yet
me too
it looks like you may want to tread lightly using floats with newlib’s
printf()
implementation.
O, nice topic. Implementing my own printf
/sprintf
can be nice to save flash memory indeed.
With forceUndefinedSymbol("_printf_float")
, I wanted to know how to do the equivalent of gcc option: -u _printf_float
I would probably choose to offer a guide on compiling/using an alternative standard lib like this one someone on the Zig discord pointed me towards. Eventually we may even have a libc written in Zig we could compile/link against.
Picolibc
is super interesting. I already tried to integrate it on STM32 without success, a long time ago. I’m going to take advantage of my learning on Zig to try again !
I was able to reduce the output bin to close/equivalent to the GCC build by adding optimization level flag when compile C source files (ex:-0g
)
By compiling the guide example, the output binary size from 18Kb is reduced to 6.3Kb
I got warning by setting elf.want_lto
to true
:
ld.lld: warning: Linking two modules of different target triples: '[...]/stm32l4_test.elf.o' is 'thumb-unknown-unknown-eabihf' whereas 'ld-temp.o' is 'thumbv7em-unknown-unknown-eabihf'
It is tracked on zig repo here
@Sazerac + anyone else this might be relevant to:
Did some more digging, and fortunately I take back what I said about weak
symbols, I believe zig/clang handles them more or less the same as GCC which is nice! My confusion came from the following behavior which I tracked down:
syscalls.c
defines neccessary system function symbols (_exit
, _kill
, etc.) AND a pre-compiled library (libnosys
) is included that ALSO defines stubs for these symbols:
Makefile
with arm-none-eabi-gcc
compiler/linker: Compiles + links fineMakefile
with zig cc
as the compiler/linker: Compiles + links finebuild.zig
file, compiles, but fails linking due to duplicate symbolsTook a while to figure out, but basically when linking, if you pass in your objects that define a symbol before a library that has that same symbol, it will simply not include the duplicate symbol and use yours. However, if you pass in the library first and then your object files, you get a duplicate symbol error since it nabbed the symbol from the library, then found your symbol (which it won’t ignore). Confirmed that the low level call to ld.lld
was including the library files before my object files in the arg list, and when I switched the order manually, it linked fine.
Updated repo with a couple of learnings + support for 0.13.0
!
Hi,
I am wondering about several points to better understand the relationships and differences between gcc, clang and zig.
libgcc.a
. An explanation of that one can be found here;
“-lgcc” is no longer included, as Zig provides its own equivalent functionality to this library
I found this part interesting.
Nice spot! Hadn’t seen this before but after some research it appears that’s just newlib-nano compiled with debug symbols included.
I didn’t have time to go further to find out what debug features this library provides.
I imagine it is interesting to include this library instead of “libc_nano.a” it if a macro is set or a selection optimization level selected (eg: -DDEBUG, -0g)
From the official documentation:
Although Zig is independent of C, and, unlike most other languages, does not depend on libc, Zig acknowledges the importance of interacting with existing C code.
When linking against libc, Zig exposes this allocator with std.heap.c_allocator.
lib.linkLibC()
? )To answer some questions:
How does the Zig language manage the implementation of these features? (Builtin Functions is a part of it ?)
Nope no built-in’s from Zig! This doesn’t involve Zig code at all. It basically provides the equivalent to libgcc
in a library called libcompiler_rt
. I’m hazy on the exact implementation but it essentially resolves how math/integer types work on your particular platform you’re compiling for. Someone else please jump in if they’re more familiar
When compiling C, clang is used. clang also has its own implementation of these routines ?
Yep, basically same answer as above.
Zig and clang use the sames routines ? Or each has its own implementation ?
I’m not quite sure what you mean here, in terms of what’s provided by libgcc
? Or something else?
I didn’t have time to go further to find out what debug features this library provides.
I imagine it is interesting to include this library instead of “libc_nano.a” it if a macro is set or a selection optimization level selected (eg: -DDEBUG, -0g)
I believe it literally just gives you a version of the library that’s compiled with debug symbols. You’ll be able to see the difference if you try to step into an actual newlib
function in your debugger. You’ll have a lot more information with the “debug” compiled version of the library.
Does the standard Zig library have dependencies with the libc (even optional)?.
std.heap.c_allocator.
could be seen as a “dependency” of libc, although if you’re purely writing Zig code and don’t care to use this allocator I don’t think there’s any “hard” dependencies on libc. Basically, there’s only dependencies if you really need to call libc functions or Zig wrappers around libc functions.
Does the custom libc integration (libc_nano.a) allow the zig standard library to use them ? ( zig expect libc activation with
lib.linkLibC()
? )
I just recently did a little research into this, short answer: no. Using our custom libc integration Zig is not aware of being linked against libc, so for instance you couldn’t use std.heap.c_allocator.
However, if you really really wanted to use newlib’s implementation of malloc as an allocator (you don’t), there’s nothing to stop you from creating an allocator-like struct in Zig that wraps these calls. See this issue here, eventually I think it will be easier to make Zig “aware” of custom libc implementations:
My bad, I understood. Zig compiler and C compiler (clang) use the same routines from libcompiler_rt
. Symbols are defined by LLVM.
When you compile with gcc, the routines are defined in the libgcc
library.
However, if you really really wanted to use newlib’s implementation of malloc as an allocator (you don’t), there’s nothing to stop you from creating an allocator-like struct in Zig that wraps these calls. See this issue here, eventually I think it will be easier to make Zig “aware” of custom libc implementations:
I was only wondering about the interactions between the Zig language and the libc. Maybe it’s the subject of another topic