Sunday, August 13, 2017

Getting started with a GCC back end

This is part two of a series “Writing a GCC back end”.

Most CPU architectures have a common subset – they have instructions doing arithmetics and bit operations on a few general registers, an instruction that can write a register to memory, and an instruction that can read from memory and place the result in a register. It is therefore easy to make a compiler that can compile simple straight-line functions by taking an existing back end and restricting it to this common subset. This is enough to start running the test suite, and it is then straightforward to address one deficiency at a time (adding additional instructions, addressing modes, ABI, etc.).

My original thought was that the RISC-V back end would be a good choice as a starting point – the architecture is fully documented, and it is a new, actively maintained, backend that does not use legacy APIs. But the RISC-V back end has lots of functionality (such as support for multiple ISA profiles, 32- and 64-bit modes, and features such as position-independent code, exception handling and debug information) and the work of reducing it became unnecessarily complicated when I tried...

I now think it is better to start from one of the minimal back ends, such as the back end for the Moxie architecture. Moxie seems to be a good choice as there is also a blog series “How To Retarget the GNU Toolchain in 21 Patches” describing step-by-step how it was developed. The blog series is old, but GCC has a very stable API, so it is essentially the same now (I once updated a GCC backend from GCC 4.3 to GCC 4.9, which were released 6 years apart, and only a few lines needed to be modified...).

One thing missing from the Moxie blog series is how to build the compiler and how to configure and run the test-suite, but I blogged about that a while back in “Running the GCC test-suite for epiphany-sim”.

Sunday, August 6, 2017

The structure of a GCC back end

This is part one of a series “Writing a GCC back end”.

The GCC back end is configured in gcc/config.host and the implementation is placed in directories machine under gcc/config and gcc/common/config where “machine” is the name of the back end (for example, i386 for the x86 architecture).

The back end places some functionality in libgcc. For example, architectures that do not have an instruction for integer division will instead generate a call to a function __divsi3 in libgcc. libgcc is configured in libgcc/config.host and target-specific files are located in a directory machine under libgcc/config.

gcc/config.gcc

config.gcc is a shell script that parses the target string (e.g. x86_64-linux-gnu) and sets variables pointing out where to find the rest of the back end and how to compile it. The variables that can be set are documented at the top of the config.gcc file.

The only variable that must be set is cpu_type that specifies machine. Most targets also set extra_objs that specifies extra object files that should be linked into the compiler, tmake_file that contains makefile fragments that compiles those extra objects (or sets makefile variables modifying the build), and tm_file that adds header files containing target-specific information.

A typical configuration for a simple target (such as ft32-unknown-elf) looks something like
cpu_type=ft32
tm_file="dbxelf.h elfos.h newlib-stdint.h ${tm_file}"

gcc/config/machine

The main part of the back end is located in gcc/config/machine. It consists of eight different components, each implemented in a separate file:
  • machine.h is included all over the compiler and contains macros defining properties of the target, such as the size of integers and pointers, number of registers, data alignment rules, etc.
  • GCC implements a generic backend where machine.c can override most of the functionality. The backend is written in C,1 so the virtual functions are handled manually with function pointers in a structure, and machine.c overrides the defaults using code of the form
    #undef TARGET_FRAME_POINTER_REQUIRED
    #define TARGET_FRAME_POINTER_REQUIRED ft32_frame_pointer_required
    static bool
    ft32_frame_pointer_required (void)
    {
      return cfun->calls_alloca;
    }
    
  • machine-protos.h contains prototypes for the external functions defined in machine.c.
  • machine.opt adds target-specific command-line options to the compiler using a record format specifying the option name, properties, and a documentation string for the --help output. For example,
    msmall-data-limit=
    Target Joined Separate UInteger Var(g_switch_value) Init(8)
    -msmall-data-limit=N    Put global and static data smaller than <number> bytes into a special section.
    
  • adds a command-line option -msmall-data-limit that has a default value 8, and is generated as an unsigned variable named g_switch_value.
  • machine.md, predicates.md, and constraints.md contain the machine description consisting of rules for instruction selection and register allocation, pipeline description, and peephole optimizations. These will be covered in detail in parts 3–7 of this series.
  • machine-modes.def defines extra machine modes for use in the low-level IR (a “machine mode” in the GCC terminology defines the size and representation of a data object. That is, it is a data type.). This is typically used for condition codes and vectors.
The GCC configuration is very flexible and everything can be overridden, so some back ends look slightly different as they, for example, add several .opt files by setting extra_options in config.gcc.

gcc/common/config/machine

The gcc/common/config/machine directory contains a file machine-common.c that can add/remove optimization passes, change the defaults for --param values, etc.

Many back ends do not need to do anything here, and this file can be disabled by setting
target_has_targetm_common=no
in config.gcc.

libgcc/config.host

The libgcc config.host works in the same way as config.gcc, but with different variables.

The only variable that must be set is cpu_type that specifies machine. Most targets also set extra_parts that specifies extra object files to include in the library and tmake_file that contains makefile fragments that add extra functionality (such as soft-float support).

A typical configuration for a simple target looks something like
cpu_type=ft32
tmake_file="$tmake_file t-softfp"
extra_parts="$extra_parts crti.o crtn.o crtbegin.o crtend.o"

libgcc/config/machine

The libgcc/config/machine directory contains extra files that may be needed for the target architecture. Simple implementations typically only contain a crti.S and crtn.S (crtbegin/crtend and the makefile support for building all of these have default implementation) and a file sfp-machine.h containing defaults for the soft-float implementation.


1. GCC is written in C++03 these days, but the structure has not been changed since it was written in C.

Friday, August 4, 2017

Writing a GCC back end

It is surprisingly easy to design a CPU (see for example Colin Riley’s blog series) and I was recently asked how hard it is to write a GCC back end for your new architecture. That too is easy — provided you have done it once before. But the first time is quite painful...

I plan to write some blog posts the coming weeks that will try to ease the pain by showing what is involved in creating a “working” back end that is capable of compiling simple functions, give some pointers to how to proceed to make this production-ready, and in general provide the overview I would have liked before I started developing my backend (GCC has a good reference manual, “GNU Compiler Collection Internals”, describing everything you need to know, but it is a bit overwhelming when you start...)

The series will cover the following (I’ll update the list with links to the posts as they become available) 
  1. The structure of a GCC back end
    • Which files you need to write/modify
  2. Getting started with a GCC back end
    • Pointers to resources describing how to set up the initial back end
  3. Low-level IR and basic instruction selection
    • How the low-level IR works
    • How the IR is lowered to instructions
    • How to write simple instruction patterns
  4. Specifying registers
    • Size/number of registers
    • Register classes
    • Allocation order
    • ABI
  5. More advanced instruction patterns
    • “All” functionality of the instruction patterns definitions
    • Examples of how to use this functionality
  6. Peephole optimizations, etc.
  7. Pipeline description
  8. Cost model
  9. ...