ELLCC Cross Compilation Configuration

In my previous post I described an initial prototype of a scheme to configure ELLCC (my build of the clang compiler and other stuff) to make it easier to use in a cross compilation development environment. I have implemented a functional prototype of the ideas I described in that post and the latest binary snapshots of ELLCC contain the functionality that I’m describing here.

First a little background on what I’m trying to accomplish. Out of the box, ELLCC supports several processors with a C/C++ compiler based on clang/LLVM (ecc), a full set of run-time support libraries (libc++, musl libc, compiler-rt and others), and a set of the GNU binutils and GDB built to support all of the targets. The VERSIONS page has a list of all the current packages contained in ELLCC. ELLCC is built to support several processor families: ARM, Microblaze, Mips, PowerPC, and X86 (32 and 64 bit). ELLCC is intended to support these processors on both Linux and standalone OS-less targets with more environments to follow.

The problem is that each of these processor and OS (or OS-less) environments have a different set of include files and run-time libraries needed to do a successful build. In addition, all of the processor families have several similar but different processor variations that might require object files created by the compiler and those placed in the run-time libraries to be created specifically for the particular processor variant.

ecc has a bazillion command line options to control include file paths, processor types, ABI settings, and all sorts of other things. It is sometimes difficult to determine the exact set of options needed to target a particular environment. I have to admit that I’m sometimes a command line kind of guy and I like to build a simple “hello world” for a particular environment. Having to remember and type in all the correct options is not an ideal situation.

Usually, the main option you need for cross compiling is the -target option, which usually looks something like this:

-target arm-ellcc-linux

The target option takes a target triple, which specifies the processor, the vendor, and the target OS. If I try to build a simple test program for an ARM processor, I get this:

[~] dev% ~/ellcc/bin/ecc -target arm-ellcc-linux test.c
/home/rich/ellcc/bin/ecc-ld: error: /tmp/test-0df798.o uses VFP register arguments, a.out does not
/home/rich/ellcc/bin/ecc-ld: failed to merge target specific data of file /tmp/test-0df798.o
ecc: error: linker command failed with exit code 1 (use -v to see invocation)
[~] dev% 

Obviously I don’t have everything that I need on the command line so I liike around and finally come up with the right combination:

[~] dev% ~/ellcc/bin/ecc -target arm-ellcc-linux test.c -march=armv7 -mfpu=vfp -mfloat-abi=softfp 
[~] dev% 

In addition, if I want to build for another ARM variant I get nice messages like this:

[~] dev% ~/ellcc/bin/ecc -target arm-ellcc-linux test.c -mcpu=cortex-m4         /home/rich/ellcc/bin/ecc-ld: error: /tmp/test-e8da00.o uses VFP register arguments, a.out does not
/home/rich/ellcc/bin/ecc-ld: error: /tmp/test-e8da00.o: Conflicting architecture profiles M/A
/home/rich/ellcc/bin/ecc-ld: failed to merge target specific data of file /tmp/test-e8da00.o
/home/rich/ellcc/bin/ecc-ld: error: /home/rich/ellcc/bin/../libecc/lib/arm/linux/libc.a(syscall.o): Conflicting CPU architectures 13/0
ecc: error: linker command failed with exit code 1 (use -v to see invocation)
[~] dev% 

I decided to add a configuration capability to ecc to allow command line options and other environment specific stuff to be specified once in a common place with the ability to easily override the defaults if necessary.

LLVM has a mechanism to read and write YAML format files easily. It is called YAMLIO. The approach I’ve taken is to use YAML format configuration information to configure ELLCC to handle all the different variations need to build for different target environments.

Now for some gory implementation details. I modified ecc to handle all target triples with the ellcc vendor with YAML formatted configuration information. The default configurations are defined in the Tools.cpp file and look something like this:

namespace {
const char ellcc_linux[] =
  "  static_default: true\n"
  "  cxx_include_dirs:\n"
  "    - $R/include/c++\n"
  "  output:\n"
  "    - -o $O\n"
  "  exe: $E/ecc-ld\n"
  "  output:\n"
  "    - -o $O\n"
  "  start:\n"
  "    - -e _start\n"
  "  opt_static:\n"
  "    - -Bstatic\n"
  "  opt_rdynamic:\n"
  "    - -export-dynamic\n"
  "  opt_dynamic:\n"
  "    - -Bdynamic\n"
  "  opt_shared:\n"
  "    - -shared\n"
  "  opt_notshared:\n"
  "    - -dynamic-linker /usr/libexec/ld.so\n"
  "  opt_pthread:\n"
  "    - -pthread\n"
  "  cxx_libraries:\n"
  "    - -lc++\n"
  "    - -lm\n"
  "  profile_libraries:\n"
  "    - -lprofile_rt\n"
  "  c_libraries:\n"
  "    - -lc\n"
  "    - -lcompiler_rt\n"

const char arm_ellcc_linux[] =
  "based_on: ellcc-linux\n"
  "  options:\n"
  "    - -target arm-ellcc-linux\n"
  "  c_include_dirs:\n"
  "    - $R/include/arm/linux\n"
  "    - $R/include/arm\n"
  "    - $R/include/linux\n"
  "    - $R/include\n"
  "  exe: $E/arm-elf-as\n"
  "  options:\n"
  "    - -m armelf_linux_eabi\n"
  "    - --build-id\n"
  "    - --hash-style=gnu\n"
  "    - --eh-frame-hdr\n"
  "  static_crt1: $R/lib/arm/linux/crt1.o\n"
  "  dynamic_crt1: $R/lib/arm/linux/Scrt1.o\n"
  "  crtbegin: $R/lib/arm/linux/crtbegin.o\n"
  "  crtend: $R/lib/arm/linux/crtend.o\n"
  "  library_paths:\n"
  "    - -L $R/lib/arm/linux\n"
  "  c_libraries:\n"
  "    - -(\n"                  // This is needed for the ARM.
  "    - -lc\n"
  "    - -lcompiler_rt\n"
  "    - -)\n"
... (more definitions here)

These definitions are registered at ecc start up time with code like this:

using namespace clang::compilationinfo;
class ELLCC {
  ELLCC() {
    CompilationInfo::DefineInfo("ellcc-linux", ellcc_linux);
    CompilationInfo::DefineInfo("arm-ellcc-linux", arm_ellcc_linux);
    CompilationInfo::DefineInfo("armeb-ellcc-linux", armeb_ellcc_linux);
    CompilationInfo::DefineInfo("i386-ellcc-linux", i386_ellcc_linux);
    CompilationInfo::DefineInfo("mips-ellcc-linux", mips_ellcc_linux);
    CompilationInfo::DefineInfo("mipsel-ellcc-linux", mipsel_ellcc_linux);
    CompilationInfo::DefineInfo("ppc-ellcc-linux", ppc_ellcc_linux);
    CompilationInfo::DefineInfo("ppc64-ellcc-linux", ppc64_ellcc_linux);
    CompilationInfo::DefineInfo("x86_64-ellcc-linux", x86_64_ellcc_linux);

Now, if I use a command line option like “-target arm-ellcc-linux”, the appropriate compliation information is found and used. Notice that there is a hierarchy in these configurations. “arm-ellcc-linux” is based on “ellcc-linux”, which means it inherits that configuration information.

I addition to the predefined configurations, I’ve added a mechanism to read configurations from a file. If the target option is specified with e.g. “-target armv7-linux” and a file exists in the current directory or a predefined config directory (ellcc/libecc/config) named “armv7-linux” then that file is read to define the compilation information. A typical configuration file looks like this:

[~/ellcc/libecc/config] dev% cat armv7-linux 
based_on: arm-ellcc-linux
    - -target arm-ellcc-linux
    - -march=armv7
    - -mfpu=vfp
    - -mfloat-abi=softfp

Note that it is based on the predefined arm-ellcc-linux configuration and just adds a few command line options.

[~] dev% ~/ellcc/bin/ecc -target armv7-linux test.c
[~] dev% 

I also added a little debugging capability. If I set a field called "dump" to true like this:
dump: true
based_on: arm-ellcc-linux
    - -target arm-ellcc-linux
    - -march=armv7
    - -mfpu=vfp
    - -mfloat-abi=softfp

I get the complete compilation configuration:

[~] dev% ~/ellcc/bin/ecc -target armv7-linux test.c
# based_on `arm-ellcc-linux'
# based_on `ellcc-linux'
dump:            true
based_on:        ''
  static_default:  true
    - -target arm-ellcc-linux
    - '-march=armv7'
    - '-mfpu=vfp'
    - '-mfloat-abi=softfp'
    - '$R/include/arm/linux'
    - '$R/include/arm'
    - '$R/include/linux'
    - '$R/include'
    - '$R/include/c++'
  exe:             '$E/arm-elf-as'                                              
    - '-o $O'                                                                   
  exe:             '$E/ecc-ld'                                                  
    - '-o $O'                                                                   
    - -e _start                                                                 
    - -Bstatic
    - -export-dynamic
    - -Bdynamic
    - -shared
    - -dynamic-linker /usr/libexec/ld.so
    - -pthread
    - -m armelf_linux_eabi
    - --build-id
    - '--hash-style=gnu'
    - --eh-frame-hdr
  static_crt1:     '$R/lib/arm/linux/crt1.o'
  dynamic_crt1:    '$R/lib/arm/linux/Scrt1.o'
  crtbegin:        '$R/lib/arm/linux/crtbegin.o'
  crtend:          '$R/lib/arm/linux/crtend.o'
    - '-L $R/lib/arm/linux'
    - '-lc++'
    - -lm
    - -lprofile_rt
    - '-('
    - -lc
    - -lcompiler_rt
    - '-)'
[~] dev% 

2 thoughts on “ELLCC Cross Compilation Configuration

  1. rich Post author

    Thanks for the link, Christopher. I will definitely use that information as a guide for setting up my configurations.



Leave a Reply

Your email address will not be published.