Tag Archives: Cortex-M

Using ELLCC to Build for the Cortex-M3

This weekend I decided to work on getting ELLCC to support the ARM Cortex-M processors. The Cortex-M processors use the Thumb 16 bit instruction set, so adding M support involves adding a configuration file for a member of the Cortex-M family and then building the run-time libraries specifically for the target processor. To start I chose the Cortex-M3. Its configuration file currently looks like this:

[~/ellcc/libecc/config] dev% cat thumb-linux-engeabi
based_on: arm-ellcc-linux
compiler:
options:
- -target arm-ellcc-linux
- -mthumb
- -mcpu=cortex-m3
- -mfpu=none
- -mfloat-abi=softfp
linker:
static_crt1: $R/lib/thumb-linux-engeabi/crt1.o
dynamic_crt1: $R/lib/thumb-linux-engeabi/Scrt1.o
crtbegin: $R/lib/thumb-linux-engeabi/crtbegin.o
crtend: $R/lib/thumb-linux-engeabi/crtend.o
library_paths:
- -L$R/lib/thumb-linux-engeabi

With the config file in place, I attempted to build ELLCC’s standard C library musl for the M. Building a library for a new configuration is pretty easy:

[~/ellcc/libecc] dev% make thumb-linux-engeabi
[~/ellcc/libecc] dev% cd
[~] dev% cd ~/ellcc/libecc
[~/ellcc/libecc] dev% make thumb-linux-engeabi

Well, it isn’t quite that simple. The musl library has several assembly files that are written for the specific target processor. If the assembly language of the desired processor doesn’t match the sources, the sources have to be modified to support the new target. Luckily Thumb instructions are mostly a subset of 32 bit ARM instructions so the changes needed to build musl for the M were fairly minimal.

The next step to support a given processor is to compiler the compiler-rt library. This library contains support functions that handle operations that the processor doesn’t support directly and compiler generated built in functions. In the case of the M processors, this includes things like floating point functions since the M3 doesn’t have a floating point unit. Building compiler-rt was probably the most challenging part of this little project. I had never fully ported the compiler-rt build rules to use the new config files properly so rather than cobbling together something that could handle building for the M, I rewrote the build rules to properly support config files.

The last step is to build the C++ standard libraries, assuming you want C++ support. I ran into one little glitch here. The M doesn’t support 64 bit atomic operations so I had to modify those operations for the M.

Now that the libraries are built, a little experimentation is in order. I decided to try out a simple “hello world”. Here’s the source:

[~/ellcc/examples/hello] dev% cat main.c
#include <stdio.h>

int main()
{
printf("hello world\n");
}

The make files in the example directories can build for multiple targets as long as config files have been set up, so I built for the M:

[~/ellcc/examples/hello] dev% make thumb-linux-engeabi
make[1]: Entering directory `/home/rich/ellcc/examples/hello'
Compiling main.c
Linking hello
make[1]: Leaving directory `/home/rich/ellcc/examples/hello'
[~/ellcc/examples/hello] dev% ~/ellcc/bin/qemu-arm hello
hello world
[~/ellcc/examples/hello] dev%

Nice! I was curious how the Thumb instruction set affected code size so I did a side by side comparison:

[~/ellcc/examples/hello] dev% size hello
text data bss dec hex filename
19540 156 1412 21108 5274 hello
[~/ellcc/examples/hello] dev% make arm-linux-engeabi
make[1]: Entering directory `/home/rich/ellcc/examples/hello'
rm -f *.o hello hello.bin hello.log elkconfig.ld
Compiling main.c
Linking hello
make[1]: Leaving directory `/home/rich/ellcc/examples/hello'
[~/ellcc/examples/hello] dev% size hello
text data bss dec hex filename
27432 156 1412 29000 7148 hello

ELLCC creates static binaries by default, so those sizes include all the standard library bits that the program needs, i.e. printf() and software floating point support functions. It is kind of cool that the M is about 15% smaller than the equivalent ARM code for this example.

I decided to try C++. Here’s the code:

[~/ellcc/examples/hellocpp] dev% cat main.cpp
#include <iostream>

int main()
{
std::cout << "hello world" << std::endl; }

Here are the results:

[~/ellcc/examples/hellocpp] dev% make thumb-linux-engeabi
make[1]: Entering directory `/home/rich/ellcc/examples/hellocpp'
Compiling main.cpp
Linking hellocpp
make[1]: Leaving directory `/home/rich/ellcc/examples/hellocpp'
[~/ellcc/examples/hellocpp] dev% ~/ellcc/bin/qemu-arm hellocpp
hello world

And a size comparison:

[~/ellcc/examples/hellocpp] dev% size hellocpp
text data bss dec hex filename
867540 500 10824 878864 d6910 hellocpp
[~/ellcc/examples/hellocpp] dev% make arm-linux-engeabi
make[1]: Entering directory `/home/rich/ellcc/examples/hellocpp'
rm -f *.o hellocpp hellocpp.bin hellocpp.log elkconfig.ld
Compiling main.cpp
Linking hellocpp
make[1]: Leaving directory `/home/rich/ellcc/examples/hellocpp'
[~/ellcc/examples/hellocpp] dev% size hellocpp
text data bss dec hex filename
1412708 500 10824 1424032 15baa0 hellocpp

Cool stuff.

Finally a size comparison of ELLCC's ecc clang/LLVM based compiler compiled for Thumb and ARM:

[~/ellcc] dev% size bin-thumb-linux-engeabi/ecc
text data bss dec hex filename
37889520 33760 63000 37986280 2439fe8 bin-thumb-linux-engeabi/ecc
[~/ellcc] dev% size bin-arm-linux-engeabi/ecc
text data bss dec hex filename
49504456 33760 63000 49601216 2f4dac0 bin-arm-linux-engeabi/ecc

Looks like the Thumb code is about 25% smaller.

Precompiled binaries supporting the Thumb and all the other ELLCC targets are available in the 0.1.10 release available on the Download page.