Monthly Archives: September 2014

A New Binary Snapshot is Available

UPDATE (September 18, 2014): The ARM binaries have been updated to support all targets.

A new binary snapshot of the ELLCC cross compilation tool chain (http://ellcc.org) is available from ftp://ellcc.org/pub.

The tool chain is built around clang/LLVM, libc++ and libc++abi, the musl standard C library, compiler-rt, and GNU binutils and gdb.

This version uses configuration scripts to tell the compiler how to compile and link programs.

An example from ~/ellcc/libecc/config:

cat ~/ellcc/libecc/config/arm-linux-engeabi:

based_on:
  arm-ellcc-linux
compiler:
  options:
    - -target arm-ellcc-linux
    - -march=armv7a
    - -mfpu=none
    - -mfloat-abi=softfp
linker:
  static_crt1: $R/lib/arm-linux-engeabi/crt1.o
  dynamic_crt1: $R/lib/arm-linux-engeabi/Scrt1.o
  crtbegin: $R/lib/arm-linux-engeabi/crtbegin.o
  crtend: $R/lib/arm-linux-engeabi/crtend.o
  library_paths:
    - -L $R/lib/arm-linux-engeabi

An example command line to compile for a specific target:

~/ellcc/bin/ecc -target arm-linux-engeabi -o hello hello.c

From the README.txt file:

The files in this directory contain pre-compiled versions of the ELLCC
cross compiler tool chain. Each tarball is meant to run on a specific
target Linux system as indicated by the name, e.g. ellcc-arm-… are
executables that are supposed to run on an little endian ARM Linux box.

    -target              Endian        Float

armeb-linux-engeabi       Big           Soft

armeb-linux-engeabihf     Big           Hard

arm-linux-engeabi         Little        Soft

arm-linux-engeabihf       Little        Hard

i386-linux-eng            Little        Hard

mipsel-linux-eng          Little        Hard

mipsel-linux-engsf        Little        Soft

mips-linux-eng            Big           Hard

mips-linux-engsf          Big           Soft

ppc-linux-eng             Big           Hard

x86_64-linux-eng          Little        Hard

All of the executables in these tarballs are statically linked, so they
should run on any reasonably recent Linux box.

Tarball contents:
C/C++ compiler (ecc, ecc++)
Assemblers for all targets
GNU binutils compiled to support all targets.
GDB compiled to support all the targets.

All of the tarballs contain header files and runtime libraries for all
the targets so you should be able to build for any one of the targets [1].

These tarballs have not been tested, except for the arm, i386 and x86_64
versions, because I don’t have access to Linux boxes running on the other
targets yet. I would welcome feedback on how they work for you.

To use the tarball:
tar xvfpz <path to your tarball>

This will create a directory called “ellcc” that will have all the
files you need to use ELLCC on the target system.

If you have any problems, please post on the ELLCC forum:
http://ellcc.org/blog/?forum=forum

Please visit http://ellcc.org for more information.

Have fun!

-Rich

[1]: The ARM versions of the executables currently can only target ARM
processors because the ecc compiler became too large to be statically
linked when all processors were supported.

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[] =
  "global:\n"
  "  static_default: true\n"
  "compiler:\n"
  "  cxx_include_dirs:\n"
  "    - $R/include/c++\n"
  "assembler:\n"
  "  output:\n"
  "    - -o $O\n"
  "linker:\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"
  "compiler:\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"
  "assembler:\n"
  "  exe: $E/arm-elf-as\n"
  "linker:\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 {
public:
  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("microblaze-ellcc-linux",
                                microblaze_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);
  }
} ELLCC;

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
compiler:
  options:
    - -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% 
Voila!

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
compiler:
  options:
    - -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:        ''
global:          
  static_default:  true
compiler:        
  options:         
    - -target arm-ellcc-linux
    - '-march=armv7'
    - '-mfpu=vfp'
    - '-mfloat-abi=softfp'
  c_include_dirs:  
    - '$R/include/arm/linux'
    - '$R/include/arm'
    - '$R/include/linux'
    - '$R/include'
  cxx_include_dirs: 
    - '$R/include/c++'
assembler:                                                                      
  exe:             '$E/arm-elf-as'                                              
  output:                                                                       
    - '-o $O'                                                                   
linker:                                                                         
  exe:             '$E/ecc-ld'                                                  
  output:                                                                       
    - '-o $O'                                                                   
  start:                                                                        
    - -e _start                                                                 
  opt_static:      
    - -Bstatic
  opt_rdynamic:    
    - -export-dynamic
  opt_dynamic:     
    - -Bdynamic
  opt_shared:      
    - -shared
  opt_notshared:   
    - -dynamic-linker /usr/libexec/ld.so
  opt_pthread:     
    - -pthread
  options:         
    - -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'
  library_paths:   
    - '-L $R/lib/arm/linux'
  cxx_libraries:   
    - '-lc++'
    - -lm
  profile_libraries: 
    - -lprofile_rt
  c_libraries:     
    - '-('
    - -lc
    - -lcompiler_rt
    - '-)'
...
[~] dev% 

An Initial Prototype of a clang Cross Compile Config File

UPDATE: The latest update to the configuration info is described here.

Today I decided to play around with some ideas for making clang a little more friendly for cross compiling. I added the ability to make the -target option look for and read in a configuration file if one is available. I build and use clang for cross compiling stuff. Up until now I’ve been using makefile magic to differentiate between different processor families and architectures. The original idea came from this mailing list post.

The thought behind my prototype is this: If a -target option is specified, or the clang executable name has a prefix (e.g. cortex-m4-ecc, ecc is my name for clang), then attempt to read a config file from the resource directory with the name of the -target argument or the executable prefix.

The config file would then be used to set things up to compile for a particular target.
Here is an example config file:

compiler:
  options:
    - -target arm-ellcc-linux-eabi5
    - -mcpu=cortex-m4
assembler:
  exe: $E/arm-elf-as
  options:
linker:
  exe: $E/ecc-ld
  options:
    - -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
  library_path: $R/lib/arm/linux

I implemented the prototype this weekend, I like the way it feels. I created a CompilationInfo class that is passed to the Driver if a config file is used. I use the class in Tools.cpp if it is available to configure the assembler and linker invocations. Cool stuff.
This week I’ll use it to build libraries and programs for a variety of ARM variants. I’d also like to think about specifying #include file paths in the config file this week.

A few more details on the implementation so far. I use the -target argument, or the prefix on the compiler name, to try to open the config file. If a file of that name isn’t found, the -target argument passes through unscathed and works as it does now. I use YAMLIO to read the configuration into a structure that currently looks like this:

  typedef std::vector StrSequence;
  struct Compiler {
    StrSequence options;
  };

  struct Assembler {
    std::string exe;
    StrSequence options;
  };

  struct Linker {
    std::string exe;
    StrSequence options;
    std::string static_crt1;
    std::string dynamic_crt1;
    std::string crtbegin;
    std::string crtend;
    std::string library_path;
  };

  struct CompilationInfo {
    Compiler compiler;
    Assembler assembler;
    Linker linker;
  };

After some good discussion on the LLVM mailing list, I realized that with a simple registration process, statically initialized info structures could be registered by preexisting drivers. This would obviously eliminate the need to read the config file for every compilation.

Does this sound like a reasonable approach?