Tag Archives: ELLCC Development

ELLCC Now Available for Windows

Last week someone mentioned that he’d like to run the ELLCC cross development tools on an operating system called “Windows”. Doing a little goolging proved that he might not be alone. Windows seems to be fairly popular, rivaling even Linux in popularity on the desktop. I decided to see what it would take to get ELLCC running on Windows. I spent the day Saturday installing MinGW-w64 on my development system and tweaking the ELLCC build rules to use it. The result is that I can now make a binary snapshot of ELLCC that runs on Windows which I have placed on the ftp site at ftp://ellcc.org/pub.

Here is an example of the results:

C:\cygwin\home\rich>ellcc\bin\ecc.exe -target x86_64-linux-eng hello.c

C:\cygwin\home\rich>ellcc\bin\ecc-size a.out
   text    data     bss     dec     hex filename
  15595     248    1608   17451    442b a.out

C:\cygwin\home\rich>

If I copy a.out to my Linux box, I get:

[~] dev% ellcc/bin/ecc-size a.out
   text    data     bss     dec     hex filename
  15595     248    1608   17451    442b a.out 
[~] dev% ./a.out 
hello world
[~] dev%

You can also use the work-in-progress ELK bare metal environment:

C:\cygwin\home\rich>ellcc\bin\ecc.exe -target arm-elk-engeabi hello.c -g

C:\cygwin\home\rich>ellcc\bin\ecc-size.exe a.out
   text    data     bss     dec     hex filename
  76923    2744   23096  102763   1916b a.out

C:\cygwin\home\rich>

If you have QEMU installed on your Windows box, you can run the bare metal executable as described in a previous blog post.

This makes it theoretically possible for me to do further development of ELLCC on Windows. I don’t think I will though.

Introducing ELK: A Bare Metal Environment for ELLCC

ELLCC (pronounced “elk”) is a C/C++ programming environment based on the clang/LLVM compiler. The goal of ELLCC is to provide a complete tool chain and run-time environment for developing C/C++ programs for embedded systems. ELLCC supports several Linux targets today, specifically ARM, Mips, PowerPC, and x86 systems. ELK (which might mean “embedded little kernel” or “embedded Linux kernel”) is a work-in-progress that allows the ELLCC tool chain to target bare metal environments where an OS is not available or needed.

What will differentiate ELK from other bare metal environments is that the goal is to provide a fairly complete Linux-like environment without the need for actually running Linux. ELK is in the design and development stage right now, but the basic functionality has been implemented based on work done using ELLCC for bare metal development and making the ELLCC tool chain easily configurable.

The thing that makes ELK fairly unique is that it uses the C and C++ libraries compiled for Linux. It does this by handling Linux system calls using the normal system call mechanism for each target. ELK has extensible system call handling capability so that new system call emulators can be added as needed. ELK is being developed by setting up the infrastructure for program start up in the bare metal environment and handling system calls and context switching in a small assembly language file. A fairly functional example for ARM can be seen here. System call support can be added easily. Here is a simple exit() system call example:

/* Handle the exit system call.
 */     
#include <bits/syscall.h>       // For syscall numbers.
#include <stdio.h>
#include <kernel.h>

// Make the simple console a loadable feature.
FEATURE(exit, exit)

static int sys_exit(int status)
{
    printf("The program has exited.\n");
    for( ;; )
      continue;
}       
        
CONSTRUCTOR()   
{       
    // Set up a simple exit system call.
    __set_syscall(SYS_exit, sys_exit);
}       

Here’s an example of a simple ARM program running on QEMU in system (bare metal) mode:

[~] dev% cat hello.cpp
#include <iostream>

int main()
{
  std::cout << "hello world" << std::endl;
}
[~] dev% ~/ellcc/bin/ecc++ -target arm-elk-engeabi hello.cpp -g
[~] dev% ~/ellcc/bin/qemu-system-arm -M vexpress-a9 -m 128M -nographic -kernel a.out
audio: Could not init `oss' audio driver
unhandled system call (256) args: 1, 268472376, 1610613215, 1610613023, 1207961673, 1241513736
unhandled system call (45) args: 0, 0, 0, 1241512408, 590752, 0
unhandled system call (45) args: 4096, 4111, 1241512408, 1241512336, 592800, 1254352
unhandled system call (192) args: 0, 8192, 3, 34, -1, 0
unhandled system call (45) args: 4096, 4111, 34, 1241512336, 592800, -1
unhandled system call (192) args: 0, 8192, 3, 34, -1, 0
hello world
unhandled system call (248) args: 0, 1207962904, 1309668, 0, 1207991180, 1241513736
The program has exited.

Notice that there are several unhandled system calls (I did say that ELK is a work-in-progress) but enough has been implemented that “hello world” comes out.
The C version is a little quieter:

[~] dev% ~/ellcc/bin/qemu-system-arm -M vexpress-a9 -m 128M -nographic -kernel a.out
audio: Could not init `oss' audio driver
unhandled system call (256) args: 1, 268472376, 1610613215, 1610613023, 1207961673, 1241513736
hello world
unhandled system call (248) args: 0, 0, 1207983756, 0, 1207985256, 1241513736
The program has exited.

The current state of ELK is that only the ARM version is at all functional. It supports multiple threads, memory allocation using malloc, semaphores, and timers. ELK started out as a couple of prototypes to test the feasibility of using Linux libraries on bare metal and to enhance the ability to configure the tool chain for multiple environments. I like the way that the development is going so far and hope to have a complete ARM implementation soon and support for the other targets soon after that. You can browse the complete source code for ELK.

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?

Using ELLCC to Build Itself, Including Canadian Cross Builds

The ELLCC tool chain can now completely build itself. Currently the build has been tested only on Linux systems. The steps I’ll use here are:

  1. Build ELLCC using the system compiler (bootstrap build)
  2. Build it again with itself (sanity build)
  3. Cross build it for another target machine (show off build)

ELLCC supports several target processors:

  • arm – little endian ARM
  • armeb – big endian ARM
  • i366 – x86 in 32 bit mode
  • microblaze – the Xilinx soft-core processor for FPGAs
  • mips – big endian Mips
  • mipsel – little endian Mips
  • ppc – the Power PC in 32 bit mode
  • x86_64 – x86 in 64 bit mode

The compiler we’ll be building can target all of them.

Go into a directory and get the latest ELLCC top of tree (TOT):

[~] dev% svn checkout http://ellcc.org/svn/ellcc/trunk ellcc

I checkout into a directory called ellcc, but the name isn’t important.
In this example. I checked out the files in my home directory. Now we can do the initial bootstrap build:

[~] dev% cd ellcc
[~/ellcc] dev% ./build

The build takes quite a while because it is building ecc, the clang/LLVM based C/C++ compiler, GNU binutils for the assemblers, linker, and utilities, GDB for debugging, and QEMU for all the target processors for testing purposes. It also builds a compete set of run-time libraries:

  • libc++ for the C++ standard library
  • libc++ABI for C++ run-time suport
  • libunwind for C++ exception handling
  • musl for the C standard library
  • compiler-rt for low-level support routines
  • ncurses for terminal support
  • zlib for compression/decompression

All of these libraries are built for all the supported target processors.

When the bootstrap build is finished, you’ll get a message like this:

Please run the build script again to bootstrap ecc.
This may be done a few times:
1. ecc is built with itself (compiled with gcc) and libecc.
2. ecc is built with itself (compiled with itself) and libecc.

Run build again. This will build the ELLCC tools with themselves to complete the bootstrap.

Just run the same build command again:

[~/ellcc] dev% ./build

This time you won’t get the message above, since ELLCC has been completely bootstrapped.

Now for the fun part, a Canadian cross build.
In this step, we’ll use our newly built compiler to build a compiler that will run on a different target system. In this case, the ELLCC build rules don’t bother with compiling QEMU, since it is only used for ELLCC development. In addition, we can skip building the libraries, since they have already been built. I’ll do a build for an ARM target:

[~/ellcc] dev%de>[~/ellcc] dev% ./build arm

When this build completes, we’ll have a new directory populated with the ARM executables, bin-arm-linux:

[~/ellcc] dev% ls bin-arm-linux/
arm-elf-as*     ecc-gdb*      i386-elf-as*       llvm-extract*     macho-dump*
bugpoint*       ecc-gprof*    llc*               llvm-link*        microblaze-elf-as*
clang-check*    ecc-ld*       lli*               llvm-mc*          mips-elf-as*
clang-format*   ecc-ld.bfd*   lli-child-target*  llvm-mcmarkup*    not*
clang-tblgen*   ecc-nm*       llvm-ar*           llvm-nm*          opt*
ecc*            ecc-objcopy*  llvm-as*           llvm-objdump*     ppc64-elf-as*
ecc++@          ecc-objdump*  llvm-bcanalyzer*   llvm-ranlib@      ppc-elf-as*
ecc-addr2line*  ecc-ranlib*   llvm-config*       llvm-readobj*     sparc-elf-as*
ecc-ar*         ecc-readelf*  llvm-config-host*  llvm-rtdyld*      x86_64-elf-as*
ecc-as*         ecc-size*     llvm-cov*          llvm-size*
ecc-c++filt*    ecc-strings*  llvm-diff*         llvm-stress*
ecc-elfedit*    ecc-strip*    llvm-dis*          llvm-symbolizer*
ecc-embedspu*   FileCheck*    llvm-dwarfdump*    llvm-tblgen*

[~/ellcc] dev% ~/ellcc[~/ellcc] dev% file bin-arm-linux/ecc
bin-arm-linux/ecc: ELF 32-bit LSB executable, ARM, version 1, statically linked, BuildID[sha1]=0x9ff616a316ab010b46062f7fc1dff554ee7a6db8, not stripped
/bin/qemu-arm bin-arm-linux/ecc -v
clang version 3.4 (trunk)
Target: arm-unknown-linux-gnu
Thread model: posix
Selected GCC installation: 
[~/ellcc] dev

Very cool!

A Major ELLCC Milestone: Building a Completely non-gnu C++ Program for Linux

ELLCC (pronounced “elk”), the embedded compiler collection based on clang/LLVM, has reached a major milestone: The ability to create C++ programs for several target processors using a set of libraries based on libraries with non-gnu licences.

ELLCC incorporates a C/C++ compiler based on clang/LLVM (ecc). The current supported target processors are ARM (both little and big endian), i386, Microblaze (the Xilinx softcore processor), Mips (both big and little endian), PowerPC (32 bit only for now), and x86_64.

The first test case, using the LLVM lit test framework, looks pretty simple:

// Compile and run for every target.
// RUN: %armexx -o %t %s && %armrun %t  | FileCheck -check-prefix=CHECK %s
// RUN: %armebexx -o %t %s && %armebrun %t | FileCheck -check-prefix=CHECK %s
// RUN: %i386exx -o %t %s && %i386run %t | FileCheck -check-prefix=CHECK %s
// RUN: %microblazeexx -o %t %s && %microblazerun %t | FileCheck -check-prefix=CHECK %s
// RUN: %mipsexx -o %t %s && %mipsrun %t | FileCheck -check-prefix=CHECK %s
// RUN: %mipselexx -o %t %s && %mipselrun %t | FileCheck -check-prefix=CHECK %s
// RUN: %ppcexx -o %t %s && %ppcrun %t | FileCheck -check-prefix=CHECK %s
// FAIL: %ppc64exx -o %t %s && %ppc64run %t | FileCheck -check-prefix=CHECK %s
// RUN: %x86_64exx -o %t %s && %x86_64run %t | FileCheck -check-prefix=CHECK %s
// CHECK: foo.i = 10
// CHECK: bye
#include <cstdio>

class Foo {
    int i;
public:
    Foo(int i) : i(i) { }
    int get() { return i; }
    ~Foo() { printf("bye\n"); }
};

int main(int argc, char** argv)
{
    Foo foo(10);
    printf("foo.i = %d\n", foo.get());
}

It does look pretty simple, but it represents:

  • ecc compiled from clang/LLVM (near) current
  • libc++ from the LLVM project built using ecc
  • libc++ABI from the LLVM project built using ecc
  • libunwind for handling non-static destructors and exceptions built using ecc
  • musl for the standard C library built using ecc
  • compiler-rt for low level processor support built using ecc

I have some more cleanup to do, but after that the next step will be a self hosted ELLCC.

After Updating: Finding Regressions

In my last post, I described how I sync up the ELLCC project with the LLVM project’s compiler, back-end, and run-time library. Today I’ll build the merged changes and look for problems that may have occurred as the result of the merge.

The first step is to build the newly merged files. I build the my modified LLVM stuff in a separate build directory called llvm-build. Full instructions for installing and building ELLCC are on the installation page. My regression build looks like this:

[~/ellcc] main% cd llvm-build/
[~/ellcc/llvm-build] main% make

I watch for compilation errors (of course!) as well as any warnings that may occur. After a successful build, install the new binaries:

[~/ellcc/llvm-build] main% make install

Now it’s time to see if anything got broken. The first step I use is to rebuild the ELLCC C run-time library, libecc. This is a good test because libecc uses a lot of language features and is compiled for all the targets:

[~/ellcc/llvm-build] main% cd ~/ellcc/libecc/obj/
[~/ellcc/libecc/obj] main% ls
arm/ i386/ Makefile microblaze/ mips/ nios2/ ppc/ ppc64/ sparc/ x86_64/
[~/ellcc/libecc/obj] main% make clean
[~/ellcc/libecc/obj] main% make

In this case, after updating to LLVM revision 132253, I got the following code generation error:

ecc: /home/rich/ellcc/llvm/lib/CodeGen/AsmPrinter/AsmPrinterDwarf.cpp:229: void llvm::AsmPrinter::EmitCFIFrameMove(const llvm::MachineMove&) const: Assertion `!Dst.isReg() && "Machine move not supported yet."' failed.

Time for a little investigation….

It turns out that in llvm/lib/Target/Mips/MipsMCAsmInfo.cpp the line

SupportsDebugInformation = true;

was added. Apparently this was a little premature. The libecc libraries are compiled with -g to enable debug information and the first file compiled chokes the code generator. I’ll comment that line out for now and see how much further I can get.

Now I’ve found something interesting. I get an assertion failure when libecc/src/c/stdlib/merge.c is compiled for Mips:

ecc: /home/rich/ellcc/llvm/lib/CodeGen/SplitKit.cpp:180: bool llvm::SplitAnalysis::calcLiveBlockInfo(): Assertion `BI.FirstUse >= Start' failed.
0 ecc 0x0000000001a6db1f
1 ecc 0x0000000001a6e65a
2 libpthread.so.0 0x00000037bea0eeb0
3 libc.so.6 0x00000037bde330c5 gsignal + 53
4 libc.so.6 0x00000037bde34a76 abort + 390
5 libc.so.6 0x00000037bde2b905 __assert_fail + 245
6 ecc 0x00000000015d7767
7 ecc 0x00000000015d7b10 llvm::SplitAnalysis::analyzeUses() + 512
...

Time to pull out LLVM’s trusty bugpoint. bugpoint is a tool that reduces a compilation failure to a smaller test case than can be attached to a bug report. The process is described here. In this case, I can guess this isn’t a front end problem because this code has been compiled for arm, i386, and microblaze targets prior to the mips build. I can also probably eliminate optimizer problems and assume it is a code generation problem.

Following the instructions here, I’ll make a reduced test case. The failing command line is:

../../../../../bin/mips-linux-ecc -c -g -Werror -MD -MP -O1 -fno-builtin -I../../../../src/c/include -I../../../../src/c/locale ../../../../src/c/stdlib/merge.c

so I’ll go into the appropriate build directory and run a slightly modified command (I added -emit-llvm -o merge.bc to get a bitcode intermediate file, I also removed the -g to reduce the size of the .bc file):

[~/ellcc] main% cd libecc/obj/mips/linux/c
[~/ellcc/libecc/obj/mips/linux/c] main% ../../../../../bin/mips-linux-ecc -c -Werror -MD -MP -O1 -fno-builtin -I../../../../src/c/include -I../../../../src/c/locale ../../../../src/c/stdlib/merge.c -emit-llvm -o merge.bc

First, I’ll try to generate code from the .bc file to verify that a code generation problem is occurring:

[~/ellcc/libecc/obj/mips/linux/c] main% ~/ellcc/bin/llc merge.bc
llc: /home/rich/ellcc/llvm/lib/CodeGen/SplitKit.cpp:180: bool llvm::SplitAnalysis::calcLiveBlockInfo(): Assertion `BI.FirstUse >= Start' failed.
0 llc 0x0000000000f49bef
1 llc 0x0000000000f4a72a
2 libpthread.so.0 0x00000037bea0eeb0
3 libc.so.6 0x00000037bde330c5 gsignal + 53
4 libc.so.6 0x00000037bde34a76 abort + 390
5 libc.so.6 0x00000037bde2b905 __assert_fail + 245
6 llc 0x0000000000bdec47
7 llc 0x0000000000bdeff0 llvm::SplitAnalysis::analyzeUses() + 512
8 llc 0x0000000000b7e1aa
9 llc 0x0000000000b67c8b llvm::RegAllocBase::allocatePhysRegs() + 267
10 llc 0x0000000000b7b31b
11 llc 0x0000000000e7919f llvm::FPPassManager::runOnFunction(llvm::Function&) + 655
12 llc 0x0000000000e7925b llvm::FPPassManager::runOnModule(llvm::Module&) + 75
13 llc 0x0000000000e78c97 llvm::MPPassManager::runOnModule(llvm::Module&) + 551
14 llc 0x0000000000e78dfb llvm::PassManagerImpl::run(llvm::Module&) + 187
15 llc 0x000000000055550a main + 4970
16 libc.so.6 0x00000037bde1ee5d __libc_start_main + 253
17 llc 0x0000000000553229
Stack dump:
0. Program arguments: /home/rich/ellcc/bin/llc merge.bc
1. Running pass 'Function Pass Manager' on module 'merge.bc'.
2. Running pass 'Greedy Register Allocator' on function '@mergesort'
Abort (core dumped)
[~/ellcc/libecc/obj/mips/linux/c] main%

Now we can use bugpoint to get a reduced test case:

[~/ellcc/libecc/obj/mips/linux/c] main% ~/ellcc/bin/bugpoint -run-llc merge.bc
...
[lots of output]
...
Checking instruction: %list2.1 = phi i8* [ undef, %if.then419 ], [ %list2.0.lcssa, %while.end415 ]
Checking instruction: tail call void @free(i8* %list2.1)

*** Attempting to perform final cleanups:
Emitted bitcode to 'bugpoint-reduced-simplified.bc'
[~/ellcc/libecc/obj/mips/linux/c] main%

I can get a readable version of the .bc file using llvm-dis and, just for fun, verify that it too fails:

[~/ellcc/libecc/obj/mips/linux/c] main% ~/ellcc/bin/llvm-dis bugpoint-reduced-simplified.bc
[~/ellcc/libecc/obj/mips/linux/c] main% ~/ellcc/bin/llc bugpoint-reduced-simplified.ll
llc: /home/rich/ellcc/llvm/lib/CodeGen/SplitKit.cpp:180: bool llvm::SplitAnalysis::calcLiveBlockInfo(): Assertion `BI.FirstUse >= Start' failed.
0 llc 0x0000000000f49bef
1 llc 0x0000000000f4a72a
2 libpthread.so.0 0x00000037bea0eeb0
3 libc.so.6 0x00000037bde330c5 gsignal + 53
4 libc.so.6 0x00000037bde34a76 abort + 390
5 libc.so.6 0x00000037bde2b905 __assert_fail + 245
6 llc 0x0000000000bdec47
7 llc 0x0000000000bdeff0 llvm::SplitAnalysis::analyzeUses() + 512
8 llc 0x0000000000b7e1aa
9 llc 0x0000000000b67c8b llvm::RegAllocBase::allocatePhysRegs() + 267
10 llc 0x0000000000b7b31b
11 llc 0x0000000000e7919f llvm::FPPassManager::runOnFunction(llvm::Function&) + 655
12 llc 0x0000000000e7925b llvm::FPPassManager::runOnModule(llvm::Module&) + 75
13 llc 0x0000000000e78c97 llvm::MPPassManager::runOnModule(llvm::Module&) + 551
14 llc 0x0000000000e78dfb llvm::PassManagerImpl::run(llvm::Module&) + 187
15 llc 0x000000000055550a main + 4970
16 libc.so.6 0x00000037bde1ee5d __libc_start_main + 253
17 llc 0x0000000000553229
Stack dump:
0. Program arguments: /home/rich/ellcc/bin/llc bugpoint-reduced-simplified.ll
1. Running pass 'Function Pass Manager' on module 'bugpoint-reduced-simplified.ll'.
2. Running pass 'Greedy Register Allocator' on function '@mergesort'
Abort (core dumped)
[~/ellcc/libecc/obj/mips/linux/c] main%

I keep a non-modified copy of the LLVM/clang sources for comparison purposes.
I’ll try the test case with the stock top of tree LLVM as a sanity check before I file a bug report:

[~] main% cd ~/vendor/llvm-build/llvm
[~/vendor/llvm-build/llvm] main% svn update
At revision 132287.
[~/vendor/llvm-build/llvm] main% cd tools/clang/
[~/vendor/llvm-build/llvm/tools/clang] main% svn update
At revision 132287.
[~/vendor/llvm-build/llvm/tools/clang] main% cd ../../../llvm
llvm/ llvm-build/
[~/vendor/llvm-build/llvm/tools/clang] main% cd ../../../llvm-build/
[~/vendor/llvm-build/llvm-build] main% make install

This build places binaries in my ~/vendor/llvm-build/bin directory. To verify the bug, I’ll use the new llc:

[~/ellcc/libecc/obj/mips/linux/c] main% ~/vendor/llvm-build/bin/llc bugpoint-reduced-simplified.ll
llc: /home/rich/vendor/llvm-build/llvm/lib/CodeGen/SplitKit.cpp:180: bool llvm::SplitAnalysis::calcLiveBlockInfo(): Assertion `BI.FirstUse >= Start' failed.
0 llc 0x0000000000f4b6cf
1 llc 0x0000000000f4c20a
2 libpthread.so.0 0x00000037bea0eeb0
3 libc.so.6 0x00000037bde330c5 gsignal + 53
4 libc.so.6 0x00000037bde34a76 abort + 390
5 libc.so.6 0x00000037bde2b905 __assert_fail + 245
6 llc 0x0000000000be0777
7 llc 0x0000000000be0b20 llvm::SplitAnalysis::analyzeUses() + 512
8 llc 0x0000000000b7fcda
9 llc 0x0000000000b697bb llvm::RegAllocBase::allocatePhysRegs() + 267
10 llc 0x0000000000b7ce4b
11 llc 0x0000000000e7ac1f llvm::FPPassManager::runOnFunction(llvm::Function&) + 655
12 llc 0x0000000000e7acdb llvm::FPPassManager::runOnModule(llvm::Module&) + 75
13 llc 0x0000000000e7a717 llvm::MPPassManager::runOnModule(llvm::Module&) + 551
14 llc 0x0000000000e7a87b llvm::PassManagerImpl::run(llvm::Module&) + 187
15 llc 0x000000000055524a main + 4970
16 libc.so.6 0x00000037bde1ee5d __libc_start_main + 253
17 llc 0x0000000000552f69
Stack dump:
0. Program arguments: /home/rich/vendor/llvm-build/bin/llc bugpoint-reduced-simplified.ll
1. Running pass 'Function Pass Manager' on module 'bugpoint-reduced-simplified.ll'.
2. Running pass 'Greedy Register Allocator' on function '@mergesort'
Abort (core dumped)
[~/ellcc/libecc/obj/mips/linux/c] main%

After renaming the test case to MipsGRAassert.ll, I submitted the bug to the LLVM project here.