Introduction

The ppci (pure python compiler infrastructure) project is a compiler written entirely in python.

The project contains:

An example usage of the low level encoding api:

>>> from ppci.arch.x86_64 import instructions, registers
>>> i = instructions.Pop(registers.rbx)
>>> i.encode()
b'['

Another example:

>>> import io
>>> from ppci.api import asm
>>> source_file = io.StringIO("""section code
... pop rbx
... push r10
... mov rdi, 42""")
>>> obj = asm(source_file, 'x86_64')
>>> obj.get_section('code').data
bytearray(b'[ARH\xbf*\x00\x00\x00\x00\x00\x00\x00')

And yet another example:

>>> import io
>>> from ppci.api import c3c, link
>>> source_file = io.StringIO("""
...  module main;
...  function void print(string txt) { }
...  function void main() { print("Hello world"); }
... """)
>>> obj = c3c([source_file], [], 'arm')
>>> obj = link([obj])

Warning

This project is in alpha state and not ready for production use!

Table of contents

Quickstart

Installation

Install ppci in a virtualenv environment:

$ virtualenv sandbox
$ source sandbox/bin/activate
(sandbox) $ pip install ppci
(sandbox) $ ppci-build.py -h

If ppci installed correcly, you will get a help message of the ppci-build.py commandline tool.

Example projects

Download and unzip examples.zip to checkout some demo projects that can be build using ppci.

stm32f4 example

To build the blinky project do the following:

$ cd examples/blinky
$ ppci-build.py

Flash the hexfile using your flashtool of choice on the stm32f4discovery board and enjoy the magic.

arduino example

To build and the arduino blink led example, follow the following commands:

$ cd examples/avr/arduino-blinky
$ ppci-build.py
$ avrdude -v -P /dev/ttyACM0 -c arduino -p m328p -U flash:w:blinky.hex

Linux x86_64 example

To build the hello world for 64-bit linux, go here:

$ cd examples/linux64/hello
$ ppci-build.py
$ ./hello

Or run the snake demo under linux:

$ cd examples/linux64/snake
$ ppci-build.py
$ ./snake

Next steps

If you have checked out the examples, head over to the api and reference sections to learn more!

Reference

Api

The ppci library provides provides an intuitive api to the compiler, assembler and other tools. For example to assemble, compile, link and objcopy the msp430 blinky example project, the api can be used as follows:

>>> from ppci.api import asm, c3c, link, objcopy
>>> march = "msp430"
>>> o1 = asm('examples/msp430/blinky/boot.asm', march)
>>> o2 = c3c(['examples/msp430/blinky/blinky.c3'], [], march)
>>> o3 = link([o2, o1], 'examples/msp430/blinky/msp430.mmap', march)
>>> objcopy(o3, 'flash', 'hex', 'blinky_msp430.hex')

Instead of using the api, a set of commandline tools are also prodived.

The api module contains a set of handy functions to invoke compilation, linking and assembling.

ppci.api.asm(source, march, debug=False)

Assemble the given source for machine march.

Parameters:
  • source (str) – can be a filename or a file like object.
  • march (str) – march can be a ppci.arch.arch.Architecture instance or a string indicating the machine architecture.
  • debug – generate debugging information
Returns:

A ppci.binutils.objectfile.ObjectFile object

>>> import io
>>> from ppci.api import asm
>>> source_file = io.StringIO("db 0x77")
>>> obj = asm(source_file, 'arm')
>>> print(obj)
CodeObject of 1 bytes
ppci.api.c3c(sources, includes, march, opt_level=0, reporter=None, debug=False)

Compile a set of sources into binary format for the given target.

Parameters:
  • sources – a collection of sources that will be compiled.
  • includes – a collection of sources that will be used for type and function information.
  • march – the architecture for which to compile.
  • reporter – reporter to write compilation report to
  • debug – include debugging information
Returns:

An object file

>>> import io
>>> from ppci.api import c3c
>>> source_file = io.StringIO("module main; var int a;")
>>> obj = c3c([source_file], [], 'arm')
>>> print(obj)
CodeObject of 4 bytes

Links the iterable of objects into one using the given layout.

Parameters:
  • objects – a collection of objects to be linked together.
  • use_runtime (bool) – also link compiler runtime functions
  • debug (bool) – when true, keep debug information. Otherwise remove this debug information from the result.
Returns:

The linked object file

>>> import io
>>> from ppci.api import asm, c3c, link
>>> asm_source = io.StringIO("db 0x77")
>>> obj1 = asm(asm_source, 'arm')
>>> c3_source = io.StringIO("module main; var int a;")
>>> obj2 = c3c([c3_source], [], 'arm')
>>> obj = link([obj1, obj2])
>>> print(obj)
CodeObject of 8 bytes
ppci.api.objcopy(obj, image_name, fmt, output_filename)

Copy some parts of an object file to an output

ppci.api.bfcompile(source, target, reporter=None)

Compile brainfuck source into binary format for the given target

Parameters:
  • source – a filename or a file like object.
  • march – a architecture instance or a string indicating the target.
Returns:

A new object.

>>> import io
>>> from ppci.api import bfcompile
>>> source_file = io.StringIO(">>[-]<<[->>+<<]")
>>> obj = bfcompile(source_file, 'arm')
>>> print(obj) 
CodeObject of ... bytes
ppci.api.construct(buildfile, targets=())

Construct the given buildfile. Raise task error if something goes wrong.

ppci.api.optimize(ir_module, level=0, reporter=None, debug_db=None)

Run a bag of tricks against the ir-code.

This is an in-place operation!

Parameters:
  • ir_module (ppci.ir.Module) – The ir module to optimize.
  • level – The optimization level, 0 is default. Can be 0,1,2 or s 0: No optimization 1: some optimization 2: more optimization s: optimize for size
ppci.api.get_arch(arch)

Try to return an architecture instance. arch can be a string in the form of arch:option1:option2

>>> from ppci.api import get_arch
>>> arch = get_arch('msp430')
>>> arch
msp430-arch
>>> type(arch)
<class 'ppci.arch.msp430.arch.Msp430Arch'>

Command line tools

This section describes the usage the commandline tools installed with ppci.

Take for example the stm32f4 blinky project. To build this project, run ppci-build.py in the project folder:

$ cd examples/blinky
$ ppci-build.py

This command is used to construct build files.

Or specify the buildfile a the command line:

$ ppci-build.py -f examples/blinky/build.xml

Instead of relying on a build system, the c3 compiler can also be activated stand alone.

$ ppci-c3c.py --machine arm examples/snake/game.c3

ppci-c3c.py

C3 compiler. Use this compiler to produce object files from c3 sources and c3 includes. C3 includes have the same format as c3 source files, but do not result in any code.

usage: ppci-c3c.py [-h] [–log log-level] [–report report-file] [–verbose] [–version] –machine {6500,arm,avr,example,msp430,riscv,x86_64} [–mtune option] –output output-file [-i include] [-g] [-O {0,1,2,s}] source [source ...]

source

source file

-h, --help

show this help message and exit

--log <log-level>

Log level (info,debug,warn)

--report

Specify a file to write the compile report to

--verbose, -v

Increase verbosity of the output

--version, -V

Display version and exit

--machine, -m

target architecture

--mtune <option>

architecture option

--output, -o

output file

-i <include>, --include <include>

include file

-g

create debug information

-O {0,1,2,s}

optimize code

ppci-build.py

Build utility. Use this to execute build files.

usage: ppci-build.py [-h] [–log log-level] [–report report-file] [–verbose] [–version] [-f build-file] [target [target ...]]

target
-h, --help

show this help message and exit

--log <log-level>

Log level (info,debug,warn)

--report

Specify a file to write the compile report to

--verbose, -v

Increase verbosity of the output

--version, -V

Display version and exit

-f <build-file>, --buildfile <build-file>

use buildfile, otherwise build.xml is the default

ppci-asm.py

Assembler utility.

usage: ppci-asm.py [-h] [–log log-level] [–report report-file] [–verbose] [–version] –machine {6500,arm,avr,example,msp430,riscv,x86_64} [–mtune option] –output output-file [-g] sourcefile

sourcefile

the source file to assemble

-h, --help

show this help message and exit

--log <log-level>

Log level (info,debug,warn)

--report

Specify a file to write the compile report to

--verbose, -v

Increase verbosity of the output

--version, -V

Display version and exit

--machine, -m

target architecture

--mtune <option>

architecture option

--output, -o

output file

-g, --debug

create debug information

ppci-ld.py

Linker. Use the linker to combine several object files and a memory layout to produce another resulting object file with images.

usage: ppci-ld.py [-h] [–log log-level] [–report report-file] [–verbose] [–version] –output output-file [–layout layout-file] [-g] obj [obj ...]

obj

the object to link

-h, --help

show this help message and exit

--log <log-level>

Log level (info,debug,warn)

--report

Specify a file to write the compile report to

--verbose, -v

Increase verbosity of the output

--version, -V

Display version and exit

--output, -o

output file

--layout <layout-file>, -L <layout-file>

memory layout

-g

retain debug information

ppci-objcopy.py

Objcopy utility to manipulate object files.

usage: ppci-objcopy.py [-h] [–log log-level] [–report report-file] [–verbose] [–version] –segment SEGMENT [–output-format OUTPUT_FORMAT] input output

input

input file

output

output file

-h, --help

show this help message and exit

--log <log-level>

Log level (info,debug,warn)

--report

Specify a file to write the compile report to

--verbose, -v

Increase verbosity of the output

--version, -V

Display version and exit

--segment <segment>, -S <segment>

segment to copy

--output-format <output_format>, -O <output_format>

output file format

ppci-objdump.py

Objdump utility to display the contents of object files.

usage: ppci-objdump.py [-h] [–log log-level] [–report report-file] [–verbose] [–version] [-d] obj

obj

object file

-h, --help

show this help message and exit

--log <log-level>

Log level (info,debug,warn)

--report

Specify a file to write the compile report to

--verbose, -v

Increase verbosity of the output

--version, -V

Display version and exit

-d, --disassemble

Disassemble contents

C3 language

Introduction

As an example language, the c3 language was created. As pointed out clearly in c2lang, the C language is widely used, but has some strange contraptions. These include the following:

  • The include system. This results in lots of code duplication and file creation. Why would you need filenames in source code?
  • The comma statement: x = a(), 2; assigns 2 to x, after calling function a.
  • C is difficult to parse with a simple parser. The parser has to know what a symbol is when it is parsed. This is also referred to as the lexer hack.

In part for these reasons (and of course, for fun), C3 was created.

The hello world example in c3 is:

module hello;
import io;

function void main()
{
    io.println("Hello world");
}

Language reference

Modules

Modules in C3 live in file, and can be defined in multiple files. Modules can import each other by using the import statement.

For example:

pkg1.c3:

module pkg1;
import pkg2;

pkg2.c3:

module pkg2;
import pkg1;
Functions

Function can be defined by using the function keyword, followed by a type and the function name.

module example;

function void compute()
{
}

function void main()
{
    main();
}
Variables

Variables require the var keyword, and can be either global are function local.

module example;

var int global_var;

function void compute()
{
    var int x = global_var + 13;
    global_var = 200 - x;
}
Types

Types can be specified when a variable is declared, and also typedeffed.

module example;
var int number;
var int* ptr_num;
type int* ptr_num_t;
var ptr_num_t number2;
If statement

The following code example demonstrates the if statement. The else part is optional.

module example;

function void compute(int a)
{
    var int b = 10;
    if (a > 100)
    {
        b += a;
    }

    if (b > 50)
    {
        b += 1000;
    }
    else
    {
        b = 2;
    }
}
While statement

The while statement can be used as follows:

module example;

function void compute(int a)
{
    var int b = 10;
    while (b > a)
    {
        b -= 1;
    }
}
For statement

The for statement works like in C. The first item is initialized before the loop. The second is the condition for the loop. The third part is executed when one run of the loop is done.

module example;

function void compute(int a)
{
    var int b = 0;
    for (b = 100; b > a; b -= 1)
    {
        // Do something here!
    }
}

Build system

It can be convenient to bundle a series of build steps into a script, for example a makefile. Instead of depending on make, yet another build tool was created. The build specification is specified in xml. Much like msbuild and Ant.

A project can contain a build.xml file which describes how the project should be build. The name of the file can be build.xml or another filename. This file can than be given to ppci-build.py.

An example build file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<project name="Snake" default="snake">
    <import name="ppci.buildtasks" />

    <target name="snake">
        <assemble
            source="../startup.asm"
            arch="arm:thumb"
            output="startup.oj" />
        <compile
            arch="arm:thumb"
            sources="../../src/snake/*.c3;../bsp.c3;../../../librt/io.c3"
            output="rest.oj"
            report="snake_report.html"/>
        <link output="snake.oj"
            layout="../memlayout.mmap" 
            objects="startup.oj;rest.oj" />
        <objcopy
            objectfile="snake.oj"
            imagename="flash"
            format="bin"
            output="snake.bin" />
    </target>

</project>

Projects

The root element of a build file is the project tag. This tag contains a name and optionally a default target attribute. When no target is given when building the project, the default target is selected.

Targets

Like make, targets can depend on eachother. Then one target is run, the build system makes sure to run depending targets first. Target elements contain a list of tasks to perform.

Tasks

The task elements are contained within target elements. Each task specifies a build action. For example the link task takes multiple object files and combines those into a merged object.

IR-code

The purpose of an intermediate representation (IR) of a program is to decouple the implementation of a front-end from the implementation of a back-end. That is, front ends generate IR-code, optimizers optimize this code and lastly backends transform it into machine code or something else.

A good IR has several characteristics:

  • It should be simple enough for front-ends to generate code.
  • It should be rich enough to exploit the target instructions best.

The IR in the ppci.ir module has the following properties:

  • It is static single assignment form. Meaning a value can only be assigned once, and is then never changed. This has several advantages.
  • It contains only basic types. Structures, arrays and void types are not represented.

Top level structure

The IR-code is implemented in the ir package.

class ppci.ir.Module(name)

Container unit for variables and functions.

add_variable(variable)

Add a variable to this module

functions

Get all functions of this module

variables

Get all variables of this module

class ppci.ir.Variable(name, amount)

Global variable, reserves room in the data area. Has name and size

class ppci.ir.SubRoutine(name)

Base class of function and procedure. These two differ in that a function returns a value, where as a procedure does not.

Design trade-off: In C, a void type is introduced to permit functions that return nothing (void). This seems somewhat artificial, but keeps things simple for the users. In pascal, the procedure and function types are explicit, and the void type is not needed. This is also the approach taken here.

So instead of a Function and Call types, we have Function, Procedure, FunctionCall and ProcedureCall types.

add_block(block)

Add a block to this function

remove_block(block)

Remove a block from this function

class ppci.ir.Procedure(name)

A procedure definition that does not return a value

class ppci.ir.Function(name, return_ty)

Represents a function.

class ppci.ir.Block(name)

Uninterrupted sequence of instructions. A block is properly terminated if its last instruction is a FinalInstruction.

add_instruction(instruction)

Add an instruction to the end of this block

is_closed

Determine whether this block is propert terminated

is_empty

Determines whether the block is empty or not

predecessors

Return all predecessing blocks

remove_instruction(instruction)

Remove instruction from block

successors

Get the direct successors of this block

Types

Only simple types are available.

ppci.ir.ptr

Pointer type

ppci.ir.i64

Signed 64-bit type

ppci.ir.i32

Signed 32-bit type

ppci.ir.i16

Signed 16-bit type

ppci.ir.i8

Signed 8-bit type

ppci.ir.u64

Unsigned 64-bit type

ppci.ir.u32

Unsigned 32-bit type

ppci.ir.u16

Unsigned 16-bit type

ppci.ir.u8 = u8

Unsigned 8-bit type

ppci.ir.f64

64-bit floating point type

ppci.ir.f32

32-bit floating point type

Instructions

The following instructions are available.

Memory instructions

class ppci.ir.Load(address, name, ty, volatile=False)

Load a value from memory

class ppci.ir.Store(value, address, volatile=False)

Store a value into memory

class ppci.ir.Alloc(name, amount)

Allocates space on the stack. The type of this value is a ptr

Data instructions

class ppci.ir.Const(value, name, ty)

Represents a constant value

class ppci.ir.Binop(a, operation, b, name, ty)

Generic binary operation

class ppci.ir.Cast(value, name, ty)

Base type conversion instruction

Control flow instructions

class ppci.ir.ProcedureCall(function_name, arguments)

Call a procedure with some arguments

class ppci.ir.FunctionCall(function_name, arguments, name, ty)

Call a function with some arguments and a return value

class ppci.ir.Jump(target)

Jump statement to another block within the same function

class ppci.ir.CJump(a, cond, b, lab_yes, lab_no)

Conditional jump to true or false labels.

class ppci.ir.Return(result)

This instruction returns a value and exits the function.

class ppci.ir.Exit

Instruction that exits the procedure.

Other

class ppci.ir.Phi(name, ty)

Imaginary phi instruction to make SSA possible.

class ppci.ir.Undefined(name, ty)

Undefined value, this value must never be used.

Abstract instruction classes

There are some abstract instructions, which cannot be used directly but serve as base classes for other instructions.

class ppci.ir.Instruction

Base class for all instructions that go into a basic block

function

Return the function this instruction is part of

class ppci.ir.Value(name, ty)

An instruction that results in a value has a type and a name

is_used

Determine whether this value is used anywhere

class ppci.ir.FinalInstruction

Final instruction in a basic block

Uml

digraph "classes_foo" {
charset="utf-8"
rankdir=BT
"0" [label="{Alloc|amount\l|}", shape="record"];
"1" [label="{Binop|a\la : property\lb\lb : property\loperation\lops : list\l|}", shape="record"];
"2" [label="{Block|first_instruction\lfunction : NoneType\linstructions : list\lis_closed\lis_empty\lis_entry\lis_used\llast_instruction\lname\lphis\lpredecessors\lreferences : set\lsuccessors\l|add_instruction()\lchange_target()\ldelete()\ldump()\linsert_instruction()\lremove_instruction()\lreplace_incoming()\l}", shape="record"];
"3" [label="{CJump|a\la : property\lb\lb : property\lcond\lconditions : list\llab_no\llab_no : property\llab_yes\llab_yes : property\l|}", shape="record"];
"4" [label="{Cast|src\lsrc : property\l|}", shape="record"];
"5" [label="{Const|value\l|}", shape="record"];
"6" [label="{Exit|targets : list\l|}", shape="record"];
"7" [label="{Expression|\l|}", shape="record"];
"8" [label="{FinalInstruction|\l|}", shape="record"];
"9" [label="{Function|return_ty\l|}", shape="record"];
"10" [label="{FunctionCall|arguments\lfunction_name\l|replace_use()\l}", shape="record"];
"11" [label="{Instruction|block : NoneType\lfunction\lis_terminator\lposition\luses : set\l|add_use()\ldel_use()\ldelete()\lremove_from_block()\lreplace_use()\l}", shape="record"];
"12" [label="{Jump|target\ltarget : property\l|}", shape="record"];
"13" [label="{JumpBase|targets\l|change_target()\ldelete()\lset_target_block()\l}", shape="record"];
"14" [label="{LiteralData|data\l|}", shape="record"];
"15" [label="{Load|address\laddress : property\lvolatile : bool\l|}", shape="record"];
"16" [label="{Module|functions\lname\lvariables\l|add_function()\ladd_variable()\lstats()\l}", shape="record"];
"17" [label="{Parameter|\l|}", shape="record"];
"18" [label="{Phi|inputs : dict\l|del_incoming()\lget_value()\lreplace_use()\lset_incoming()\l}", shape="record"];
"19" [label="{Procedure|\l|}", shape="record"];
"20" [label="{ProcedureCall|arguments\lfunction_name\l|replace_use()\l}", shape="record"];
"21" [label="{Return|result\lresult : property\ltargets : list\l|}", shape="record"];
"22" [label="{Store|address\laddress : property\lvalue\lvalue : property\lvolatile : bool\l|}", shape="record"];
"23" [label="{SubRoutine|arguments : list\lblock_names\lblocks : list\ldefined_names : set\lentry : NoneType\llogger : NoneType, RootLogger\lname\lunique_counter : int\l|add_block()\ladd_parameter()\lcalc_reachable_blocks()\ldelete_unreachable()\ldump()\lmake_unique_name()\lnum_instructions()\lremove_block()\l}", shape="record"];
"24" [label="{Typ|name\l|}", shape="record"];
"25" [label="{Undefined|\l|}", shape="record"];
"26" [label="{Value|is_used\lname\lty\luse_count\lused_by : set\l|add_user()\ldel_user()\lreplace_by()\lused_in_blocks()\l}", shape="record"];
"27" [label="{Variable|amount\l|}", shape="record"];
"0" -> "7" [arrowhead="empty", arrowtail="none"];
"1" -> "7" [arrowhead="empty", arrowtail="none"];
"3" -> "13" [arrowhead="empty", arrowtail="none"];
"4" -> "7" [arrowhead="empty", arrowtail="none"];
"5" -> "7" [arrowhead="empty", arrowtail="none"];
"6" -> "8" [arrowhead="empty", arrowtail="none"];
"7" -> "26" [arrowhead="empty", arrowtail="none"];
"8" -> "11" [arrowhead="empty", arrowtail="none"];
"9" -> "23" [arrowhead="empty", arrowtail="none"];
"10" -> "7" [arrowhead="empty", arrowtail="none"];
"12" -> "13" [arrowhead="empty", arrowtail="none"];
"13" -> "8" [arrowhead="empty", arrowtail="none"];
"14" -> "7" [arrowhead="empty", arrowtail="none"];
"15" -> "26" [arrowhead="empty", arrowtail="none"];
"17" -> "7" [arrowhead="empty", arrowtail="none"];
"18" -> "26" [arrowhead="empty", arrowtail="none"];
"19" -> "23" [arrowhead="empty", arrowtail="none"];
"20" -> "11" [arrowhead="empty", arrowtail="none"];
"21" -> "8" [arrowhead="empty", arrowtail="none"];
"22" -> "11" [arrowhead="empty", arrowtail="none"];
"25" -> "26" [arrowhead="empty", arrowtail="none"];
"26" -> "11" [arrowhead="empty", arrowtail="none"];
"27" -> "7" [arrowhead="empty", arrowtail="none"];
}

Debug

When an application is build, it often has to be debugged. This section describes the peculiarities of debugging.

Debugger

The debugger class is the main piece of the debugger. This is created for a specific architecture and is given a driver to communicate with the target hardware.

class ppci.binutils.dbg.Debugger(arch, driver)

Main interface to the debugger. Give it a target architecture for which it must debug and driver plugin to connect to hardware.

clear_breakpoint(filename, row)

Remove a breakpoint

read_mem(address, size)

Read binary data from memory location

run()

Run the program

set_breakpoint(filename, row)

Set a breakpoint

step()

Single step the debugged program

stop()

Interrupt the currently running program

write_mem(address, data)

Write binary data to memory location

One of the classes that uses the debugger is the debug command line interface.

class ppci.binutils.dbg_cli.DebugCli(debugger)

Implement a console-based debugger interface.

To connect to your favorite hardware, subclass the DebugDriver class.

class ppci.binutils.dbg.DebugDriver

Inherit this class to expose a target interface. This class implements primitives for a given hardware target.

The following class can be used to connect to a gdb server:

class ppci.binutils.dbg_gdb_client.GdbDebugDriver(arch, port=1234)

Implement debugging via the GDB remote interface.

GDB servers can communicate via the RSP protocol.

Helpfull resources:

http://www.embecosm.com/appnotes/ean4/
embecosm-howto-rsp-server-ean4-issue-2.html

Debug info file formats

Debug information is of a complex nature. Various file formats exist to store this information. This section gives a short overview of the different formats.

pdb format

This is the microsoft debug format.

https://en.wikipedia.org/wiki/Program_database

Dwarf format

How a linked list is stored in dwarf format.

struct ll {
  int a;
  struct ll *next;
};
<1><57>: Abbrev Number: 3 (DW_TAG_base_type)
    <58>   DW_AT_byte_size   : 4
    <59>   DW_AT_encoding    : 5    (signed)
    <5a>   DW_AT_name        : int
 <1><5e>: Abbrev Number: 2 (DW_TAG_base_type)
    <5f>   DW_AT_byte_size   : 8
    <60>   DW_AT_encoding    : 5    (signed)
    <61>   DW_AT_name        : (indirect string, offset: 0x65): long int
 <1><65>: Abbrev Number: 2 (DW_TAG_base_type)
    <66>   DW_AT_byte_size   : 8
    <67>   DW_AT_encoding    : 7    (unsigned)
    <68>   DW_AT_name        : (indirect string, offset: 0xf6): sizetype
 <1><6c>: Abbrev Number: 2 (DW_TAG_base_type)
    <6d>   DW_AT_byte_size   : 1
    <6e>   DW_AT_encoding    : 6    (signed char)
    <6f>   DW_AT_name        : (indirect string, offset: 0x109): char
 <1><73>: Abbrev Number: 4 (DW_TAG_structure_type)
    <74>   DW_AT_name        : ll
    <77>   DW_AT_byte_size   : 16
    <78>   DW_AT_decl_file   : 1
    <79>   DW_AT_decl_line   : 4
    <7a>   DW_AT_sibling     : <0x95>
 <2><7e>: Abbrev Number: 5 (DW_TAG_member)
    <7f>   DW_AT_name        : a
    <81>   DW_AT_decl_file   : 1
    <82>   DW_AT_decl_line   : 5
    <83>   DW_AT_type        : <0x57>
    <87>   DW_AT_data_member_location: 0
 <2><88>: Abbrev Number: 6 (DW_TAG_member)
    <89>   DW_AT_name        : (indirect string, offset: 0xf1): next
    <8d>   DW_AT_decl_file   : 1
    <8e>   DW_AT_decl_line   : 6
    <8f>   DW_AT_type        : <0x95>
    <93>   DW_AT_data_member_location: 8
 <2><94>: Abbrev Number: 0
 <1><95>: Abbrev Number: 7 (DW_TAG_pointer_type)
    <96>   DW_AT_byte_size   : 8
    <97>   DW_AT_type        : <0x73>

Architecture

The arch contains processor architecture descriptions.

class ppci.arch.arch.Architecture(options=None, register_classes=())

Base class for all targets

between_blocks(frame)

Generate any instructions here if needed between two blocks

determine_arg_locations(arg_types)

Determine argument location for a given function

determine_rv_location(ret_type)

Determine the location of a return value of a function given the type of return value

epilogue(frame)

Generate instructions for the epilogue of a frame

gen_call(value)

Generate a sequence of instructions for a call to a label. The actual call instruction is not yet used until the end of the code generation at which time the live variables are known.

gen_copy_rv(res_type, res_var)

Generate a sequence of instructions for copying the result of a function to the correct variable. Override this function when needed. The basic implementation simply moves the result.

gen_fill_arguments(arg_types, args, live)

Generate a sequence of instructions that puts the arguments of a function to the right place.

get_compiler_rt_lib()

Gets the runtime for the compiler. Returns an object with the compiler runtime for this architecture

get_reg_class(bitsize=None, ty=None)

Look for a register class

get_reloc(name)

Retrieve a relocation identified by a name

get_size(typ)

Get type of ir type

has_option(name)

Check for an option setting selected

make_call(frame, vcall)

Actual call instruction implementation

make_id_str()

Return a string uniquely identifying this machine

move(dst, src)

Generate a move from src to dst

new_frame(frame_name, function)

Create a new frame with name frame_name for an ir-function

prologue(frame)

Generate instructions for the epilogue of a frame

runtime

Gets the runtime for the compiler. Returns an object with the compiler runtime for this architecture

value_classes

Get a mapping from ir-type to the proper register class

class ppci.arch.arch.Frame(name, arg_locs, live_in, rv, live_out)

Activation record abstraction. This class contains a flattened function. Instructions are selected and scheduled at this stage. Frames differ per machine. The only thing left to do for a frame is register allocation.

add_constant(value)

Add constant literal to constant pool

alloc(size)

Allocate space on the stack frame and return the offset

emit(ins)

Append an abstract instruction to the end of this frame

insert_code_after(instruction, code)

Insert a code sequence after an instruction

insert_code_before(instruction, code)

Insert a code sequence before an instruction

live_ranges(vreg)

Determine the live range of some register

live_regs_over(instruction)

Determine what registers are live along an instruction. Useful to determine if registers must be saved when making a call

new_label()

Generate a unique new label

new_name(salt)

Generate a new unique name

new_reg(cls, twain='')

Retrieve a new virtual register

class ppci.arch.isa.Isa

Container type for an instruction set. Contains a list of instructions, mappings from intermediate code to instructions.

Isa’s can be merged into new isa’s which can be used to define target. For example the arm without FPU can be combined with the FPU isa to expand the supported functions.

add_instruction(i)

Register an instruction into this ISA

pattern(non_term, tree, condition=None, size=1, cycles=1, energy=1)

Decorator function that adds a pattern.

peephole(function)

Add a peephole optimization function

register_pattern(pattern)

Add a pattern to this isa

register_relocation(relocation)

Register a relocation into this isa

class ppci.arch.isa.Register(name, num=None, aliases=())

Baseclass of all registers types

is_colored

Determine whether the register is colored

class ppci.arch.isa.Instruction(*args, **kwargs)

Base instruction class. Instructions are automatically added to an isa object. Instructions are created in the following ways:

  • From python code, by using the instruction directly: self.stream.emit(Mov(r1, r2))
  • By the assembler. This is done via a generated parser.
  • By the instruction selector. This is done via pattern matching rules

Instructions can then be emitted to output streams.

An instruction can be colored or not. When all its used registers are colored, the instruction is also colored.

classmethod decode(data)

Decode data into an instruction of this class

defined_registers

Return a set of all defined registers

encode()

Encode the instruction into binary form, returns bytes for this instruction.

is_colored

Determine whether all registers of this instruction are colored

reads_register(register)

Check if this instruction reads the given register

registers

Determine all registers used by this instruction

replace_register(old, new)

Replace a register usage with another register

set_all_patterns()

Look for all patterns and apply them to the tokens

used_registers

Return a set of all registers used by this instruction

writes_register(register)

Check if this instruction writes the given register

Backends

This page lists the available backends.

Status matrix:

feature 6500 arm avr msp430 riscv stm8 x86_64
Samples build   yes yes yes yes   yes
Samples run   yes         yes
gdb remote client     yes   yes    
percentage complete 1% 70% 50% 20% 70% 1% 60%

6500

class ppci.arch.mos6500.Mos6500Arch(options=None)

arm

Arm machine specifics. The arm target has several options:

  • thumb: enable thumb mode, emits thumb code
class ppci.arch.arm.ArmArch(options=None)

Arm machine class.

avr

The is the avr backend.

class ppci.arch.avr.AvrArch(options=None)

Avr architecture description.

class ppci.arch.avr.registers.AvrRegister(name, num=None, aliases=())
class ppci.arch.avr.registers.AvrWordRegister(name, num=None, aliases=())

Register covering two 8 bit registers

digraph "classes_foo" {
charset="utf-8"
rankdir=BT
"0" [label="{Adc|patterns : list\lrd : InstructionProperty\lrr : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"1" [label="{Add|patterns : list\lrd : InstructionProperty\lrr : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"2" [label="{Addw|rd : InstructionProperty\lrr : InstructionProperty\lsyntax : Syntax\ltokens : list\l|render()\l}", shape="record"];
"3" [label="{And|patterns : list\lrd : InstructionProperty\lrr : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"4" [label="{Andw|rd : InstructionProperty\lrr : InstructionProperty\lsyntax : Syntax\l|render()\l}", shape="record"];
"5" [label="{Asr|patterns : list\lrd : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"6" [label="{AvrArch|assembler : BaseAssembler\lfp\lgdb_pc\lgdb_registers : tuple\lisa : Isa\lname : str\l|between_blocks()\ldetermine_arg_locations()\ldetermine_rv_location()\lepilogue()\lgen_copy_rv()\lgen_fill_arguments()\lget_runtime()\llitpool()\lmake_call()\lmove()\lprologue()\l}", shape="record"];
"7" [label="{AvrArithmaticToken|d : _p2\lop : _p2\lr : _p2\l|}", shape="record"];
"8" [label="{AvrInstruction|isa : Isa\l|}", shape="record"];
"9" [label="{AvrPointerRegister|\l|}", shape="record"];
"10" [label="{AvrProgramCounterRegister|bitsize : int\l|}", shape="record"];
"11" [label="{AvrPseudo16Register|\l|}", shape="record"];
"12" [label="{AvrRegCombo|hi\llo\l|}", shape="record"];
"13" [label="{AvrRegister|bitsize : int\lsyntaxi : tuple\l|}", shape="record"];
"14" [label="{AvrSpecialRegister|\l|}", shape="record"];
"15" [label="{AvrStackPointerRegister|bitsize : int\l|}", shape="record"];
"16" [label="{AvrStatusRegister|bitsize : int\l|}", shape="record"];
"17" [label="{AvrToken|b0 : _p2\lb1 : _p2\ln0 : _p2\ln1 : _p2\ln2 : _p2\ln3 : _p2\lw0 : _p2\l|}", shape="record"];
"18" [label="{AvrToken2|d : _p2\lop : _p2\lop2 : _p2\l|}", shape="record"];
"19" [label="{AvrToken3|a : _p2\ld : _p2\lop : _p2\l|}", shape="record"];
"20" [label="{AvrToken4|d : _p2\lk : _p2\lop : _p2\l|}", shape="record"];
"21" [label="{AvrToken5|b : _p2\lop : _p2\l|}", shape="record"];
"22" [label="{AvrWordRegister|bitsize : int\lhi\llo\lsyntaxi : tuple\l|}", shape="record"];
"23" [label="{Breq|lab : InstructionProperty\lpatterns : list\lsyntax : Syntax\ltokens : list\l|relocations()\l}", shape="record"];
"24" [label="{Brge|lab : InstructionProperty\lpatterns : list\lsyntax : Syntax\ltokens : list\l|relocations()\l}", shape="record"];
"25" [label="{Brlt|lab : InstructionProperty\lpatterns : list\lsyntax : Syntax\ltokens : list\l|relocations()\l}", shape="record"];
"26" [label="{Brne|lab : InstructionProperty\lpatterns : list\lsyntax : Syntax\ltokens : list\l|relocations()\l}", shape="record"];
"27" [label="{Call|lab : InstructionProperty\lpatterns : list\lsyntax : Syntax\ltokens : list\l|relocations()\l}", shape="record"];
"28" [label="{Cp|patterns : list\lrd : InstructionProperty\lrr : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"29" [label="{Cpc|patterns : list\lrd : InstructionProperty\lrr : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"30" [label="{Cpw|rd : InstructionProperty\lrr : InstructionProperty\lsyntax : Syntax\l|render()\l}", shape="record"];
"31" [label="{Dec|patterns : list\lrd : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"32" [label="{Eor|patterns : list\lrd : InstructionProperty\lrr : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"33" [label="{HighAvrRegister|syntaxi : tuple\l|}", shape="record"];
"34" [label="{HighAvrWordRegister|\l|}", shape="record"];
"35" [label="{Imm16Token|imm : _p2\l|}", shape="record"];
"36" [label="{In|na : InstructionProperty\lpatterns : list\lrd : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"37" [label="{Inc|patterns : list\lrd : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"38" [label="{Ld|patterns : list\lrd : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"39" [label="{LdPostInc|patterns : list\lrd : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"40" [label="{LdPreDec|patterns : list\lrd : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"41" [label="{Ldi|nk : InstructionProperty\lpatterns : list\lrd : InstructionProperty\lreg_num\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"42" [label="{LdiHiAddr|lab : InstructionProperty\lpatterns : list\lrd : InstructionProperty\lreg_num\lsyntax : Syntax\ltokens : list\l|relocations()\l}", shape="record"];
"43" [label="{LdiLoAddr|lab : InstructionProperty\lpatterns : list\lrd : InstructionProperty\lreg_num\lsyntax : Syntax\ltokens : list\l|relocations()\l}", shape="record"];
"44" [label="{Ldiw|nk : InstructionProperty\lrd : InstructionProperty\lsyntax : Syntax\l|render()\l}", shape="record"];
"45" [label="{LdiwAddr|lab : InstructionProperty\lrd : InstructionProperty\lsyntax : Syntax\l|render()\l}", shape="record"];
"46" [label="{Lds|imm : InstructionProperty\lpatterns : list\lrd : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"47" [label="{LpmPostInc|patterns : list\lrd : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"48" [label="{Lsr|patterns : list\lrd : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"49" [label="{Mov|patterns : list\lrd : InstructionProperty\lrr : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"50" [label="{Movw|patterns : list\lrd : InstructionProperty\lrd_num\lrr : InstructionProperty\lrr_num\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"51" [label="{Nop|patterns : list\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"52" [label="{Or|patterns : list\lrd : InstructionProperty\lrr : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"53" [label="{Orw|rd : InstructionProperty\lrr : InstructionProperty\lsyntax : Syntax\l|render()\l}", shape="record"];
"54" [label="{Out|na : InstructionProperty\lpatterns : list\lrd : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"55" [label="{Pop|patterns : list\lrd : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"56" [label="{PseudoAvrInstruction|\l|}", shape="record"];
"57" [label="{Push|patterns : list\lrd : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"58" [label="{Ret|patterns : list\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"59" [label="{Reti|patterns : list\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"60" [label="{Rjmp|lab : InstructionProperty\lpatterns : list\lsyntax : Syntax\ltokens : list\l|relocations()\l}", shape="record"];
"61" [label="{Ror|patterns : list\lrd : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"62" [label="{Sbc|patterns : list\lrd : InstructionProperty\lrr : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"63" [label="{St|patterns : list\lrd : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"64" [label="{StPostInc|patterns : list\lrd : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"65" [label="{StPreDec|patterns : list\lrd : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"66" [label="{StWord|rd : InstructionProperty\lsyntax : Syntax\l|render()\l}", shape="record"];
"67" [label="{Sts|imm : InstructionProperty\lpatterns : list\lrd : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"68" [label="{Sub|patterns : list\lrd : InstructionProperty\lrr : InstructionProperty\lsyntax : Syntax\ltokens : list\l|}", shape="record"];
"69" [label="{Subw|rd : InstructionProperty\lrr : InstructionProperty\lsyntax : Syntax\l|render()\l}", shape="record"];
"0" -> "8" [arrowhead="empty", arrowtail="none"];
"1" -> "8" [arrowhead="empty", arrowtail="none"];
"2" -> "56" [arrowhead="empty", arrowtail="none"];
"3" -> "8" [arrowhead="empty", arrowtail="none"];
"4" -> "56" [arrowhead="empty", arrowtail="none"];
"5" -> "8" [arrowhead="empty", arrowtail="none"];
"7" -> "17" [arrowhead="empty", arrowtail="none"];
"9" -> "34" [arrowhead="empty", arrowtail="none"];
"10" -> "14" [arrowhead="empty", arrowtail="none"];
"11" -> "12" [arrowhead="empty", arrowtail="none"];
"15" -> "14" [arrowhead="empty", arrowtail="none"];
"16" -> "14" [arrowhead="empty", arrowtail="none"];
"18" -> "17" [arrowhead="empty", arrowtail="none"];
"19" -> "17" [arrowhead="empty", arrowtail="none"];
"20" -> "17" [arrowhead="empty", arrowtail="none"];
"21" -> "17" [arrowhead="empty", arrowtail="none"];
"23" -> "8" [arrowhead="empty", arrowtail="none"];
"24" -> "8" [arrowhead="empty", arrowtail="none"];
"25" -> "8" [arrowhead="empty", arrowtail="none"];
"26" -> "8" [arrowhead="empty", arrowtail="none"];
"27" -> "8" [arrowhead="empty", arrowtail="none"];
"28" -> "8" [arrowhead="empty", arrowtail="none"];
"29" -> "8" [arrowhead="empty", arrowtail="none"];
"30" -> "56" [arrowhead="empty", arrowtail="none"];
"31" -> "8" [arrowhead="empty", arrowtail="none"];
"32" -> "8" [arrowhead="empty", arrowtail="none"];
"33" -> "13" [arrowhead="empty", arrowtail="none"];
"34" -> "22" [arrowhead="empty", arrowtail="none"];
"36" -> "8" [arrowhead="empty", arrowtail="none"];
"37" -> "8" [arrowhead="empty", arrowtail="none"];
"38" -> "8" [arrowhead="empty", arrowtail="none"];
"39" -> "8" [arrowhead="empty", arrowtail="none"];
"40" -> "8" [arrowhead="empty", arrowtail="none"];
"41" -> "8" [arrowhead="empty", arrowtail="none"];
"42" -> "8" [arrowhead="empty", arrowtail="none"];
"43" -> "8" [arrowhead="empty", arrowtail="none"];
"44" -> "56" [arrowhead="empty", arrowtail="none"];
"45" -> "56" [arrowhead="empty", arrowtail="none"];
"46" -> "8" [arrowhead="empty", arrowtail="none"];
"47" -> "8" [arrowhead="empty", arrowtail="none"];
"48" -> "8" [arrowhead="empty", arrowtail="none"];
"49" -> "8" [arrowhead="empty", arrowtail="none"];
"50" -> "8" [arrowhead="empty", arrowtail="none"];
"51" -> "8" [arrowhead="empty", arrowtail="none"];
"52" -> "8" [arrowhead="empty", arrowtail="none"];
"53" -> "56" [arrowhead="empty", arrowtail="none"];
"54" -> "8" [arrowhead="empty", arrowtail="none"];
"55" -> "8" [arrowhead="empty", arrowtail="none"];
"57" -> "8" [arrowhead="empty", arrowtail="none"];
"58" -> "8" [arrowhead="empty", arrowtail="none"];
"59" -> "8" [arrowhead="empty", arrowtail="none"];
"60" -> "8" [arrowhead="empty", arrowtail="none"];
"61" -> "8" [arrowhead="empty", arrowtail="none"];
"62" -> "8" [arrowhead="empty", arrowtail="none"];
"63" -> "8" [arrowhead="empty", arrowtail="none"];
"64" -> "8" [arrowhead="empty", arrowtail="none"];
"65" -> "8" [arrowhead="empty", arrowtail="none"];
"66" -> "56" [arrowhead="empty", arrowtail="none"];
"67" -> "8" [arrowhead="empty", arrowtail="none"];
"68" -> "8" [arrowhead="empty", arrowtail="none"];
"69" -> "56" [arrowhead="empty", arrowtail="none"];
"9" -> "6" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="fp", style="solid"];
"10" -> "6" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="gdb_pc", style="solid"];
}

See also:

https://gcc.gnu.org/wiki/avr-gcc

msp430

To flash the msp430 board, the following program can be used:

http://www.ti.com/tool/msp430-flasher

class ppci.arch.msp430.Msp430Arch(options=None)

Texas Instruments msp430 target architecture

risc-v

See also: http://riscv.org

Contributed by Michael.

class ppci.arch.riscv.RiscvArch(options=None)

stm8

STM8 is an 8-bit processor, see also: http://www.st.com/stm8

x86_64

For a good list of op codes, checkout:

http://ref.x86asm.net/coder64.html

For an online assembler, checkout:

https://defuse.ca/online-x86-assembler.htm

Linux

For a good list of linux system calls, refer:

http://blog.rchapman.org/post/36801038863/linux-system-call-table-for-x86-64

class ppci.arch.x86_64.X86_64Arch(options=None)

x86_64 architecture

How to write a new backend

This section describes how to add a new backend. The best thing to do is to take a look at existing backends, like the backends for ARM and X86_64.

A backend consists of the following parts:

  1. register descriptions
  2. instruction descriptions
  3. template descriptions
  4. architecture description

Register description

A backend must describe what kinds of registers are available. To do this define for each register class a subclass of ppci.arch.isa.Register.

There may be several register classes, for example 8-bit and 32-bit registers. It is also possible that these classes overlap.

from ppci.arch.isa import Register

class X86Register(Register):
    bitsize=32

class LowX86Register(Register):
    bitsize=8

AL = LowX86Register('al', num=0)
AH = LowX86Register('ah', num=4)
EAX = X86Register('eax', num=0, aliases(AL, AH))

Architecture description

Hexfile manipulation

class ppci.utils.hexfile.HexFile

Represents an intel hexfile

>>> from ppci.utils.hexfile import HexFile
>>> h = HexFile()
>>> h.dump()
Hexfile containing 0 bytes
>>> h.add_region(0, bytes([1,2,3]))
>>> h
Hexfile containing 3 bytes

Compiler design

This chapter describes the design of the compiler. The compiler consists a frontend, mid-end and back-end. The frontend deals with source file parsing and semantics checking. The mid-end performs optimizations. This is optional. The back-end generates machine code. The front-end produces intermediate code. This is a simple representation of the source. The back-end can accept this kind of representation.

The compiler is greatly influenced by the LLVM design.

digraph x {
rankdir="LR"
1 [label="c3 source file"]
10 [label="c3 front end" ]
11 [label="language X front end" ]
20 [label="mid end" ]
30 [label="back end for X86" ]
31 [label="back end for ARM" ]
40 [label="object file"]
1 -> 10
10 -> 20 [label="IR-code"]
11 -> 20 [label="IR-code"]
20 -> 30 [label="IR-code"]
20 -> 31 [label="IR-code"]
30 -> 40
}

C3 Front-end

This is the c3 language front end. For the front-end a recursive descent parser is created.

digraph c3 {
rankdir="LR"
1 [label="source text"]
10 [label="lexer" ]
20 [label="parser" ]
40 [label="code generation"]
99 [label="IR-code object"]
1 -> 10
10 -> 20
20 -> 40
40 -> 99
}

class ppci.lang.c3.C3Builder(diag, arch)

Generates IR-code from c3 source. Reports errors to the diagnostics system.

class ppci.lang.c3.Lexer(diag)

Generates a sequence of token from an input stream

class ppci.lang.c3.Parser(diag)

Parses sourcecode into an abstract syntax tree (AST)

class ppci.lang.c3.CodeGenerator(diag, debug_db)

Generates intermediate (IR) code from a package. The entry function is ‘genModule’. The main task of this part is to rewrite complex control structures, such as while and for loops into simple conditional jump statements. Also complex conditional statements are simplified. Such as ‘and’ and ‘or’ statements are rewritten in conditional jumps. And structured datatypes are rewritten.

Type checking is done in one run with code generation.

Brainfuck frontend

The compiler has a front-end for the brainfuck language.

class ppci.lang.bf.BrainFuckGenerator(arch)

Brainfuck is a language that is so simple, the entire front-end can be implemented in one pass.

IR-code

The intermediate representation (IR) of a program de-couples the front end from the backend of the compiler.

See IR-code for details about all the available instructions.

Optimization

The IR-code generated by the front-end can be optimized in many ways. The compiler does not have the best way to optimize code, but instead has a bag of tricks it can use.

class ppci.opt.Mem2RegPromotor(debug_db)

Tries to find alloc instructions only used by load and store instructions and replace them with values and phi nodes

class ppci.opt.LoadAfterStorePass(debug_db)

Remove load after store to the same location.

[x] = a
b = [x]
c = b + 2

transforms into:

[x] = a
c = a + 2
class ppci.opt.DeleteUnusedInstructionsPass(debug_db)

Remove unused variables from a block

class ppci.opt.RemoveAddZeroPass(debug_db)

Replace additions with zero with the value itself. Replace multiplication by 1 with value itself.

class ppci.opt.CommonSubexpressionEliminationPass(debug_db)

Replace common sub expressions (cse) with the previously defined one.

Uml

digraph "classes_foo" {
charset="utf-8"
rankdir=BT
"0" [label="{BlockPass|\l|on_block()\lon_function()\l}", shape="record"];
"1" [label="{CleanPass|\l|find_empty_blocks()\lfind_single_predecessor_block()\lglue_blocks()\lon_function()\lremove_empty_blocks()\lremove_one_preds()\l}", shape="record"];
"2" [label="{CommonSubexpressionEliminationPass|\l|on_block()\l}", shape="record"];
"3" [label="{ConstantFolder|ops : dict\l|eval_const()\lis_const()\lon_block()\l}", shape="record"];
"4" [label="{DeleteUnusedInstructionsPass|\l|on_block()\l}", shape="record"];
"5" [label="{FunctionPass|\l|on_function()\lrun()\l}", shape="record"];
"6" [label="{InstructionPass|\l|on_block()\lon_instruction()\l}", shape="record"];
"7" [label="{LoadAfterStorePass|\l|find_store_backwards()\lon_block()\lremove_redundant_stores()\lreplace_load_after_store()\l}", shape="record"];
"8" [label="{Mem2RegPromotor|\l|on_function()\lplace_phi_nodes()\lpromote()\lrename()\l}", shape="record"];
"9" [label="{ModulePass|debug_db\llogger : NoneType, RootLogger\l|prepare()\l}", shape="record"];
"10" [label="{RemoveAddZeroPass|\l|on_instruction()\l}", shape="record"];
"0" -> "5" [arrowhead="empty", arrowtail="none"];
"1" -> "5" [arrowhead="empty", arrowtail="none"];
"2" -> "0" [arrowhead="empty", arrowtail="none"];
"3" -> "0" [arrowhead="empty", arrowtail="none"];
"4" -> "0" [arrowhead="empty", arrowtail="none"];
"5" -> "9" [arrowhead="empty", arrowtail="none"];
"6" -> "0" [arrowhead="empty", arrowtail="none"];
"7" -> "0" [arrowhead="empty", arrowtail="none"];
"8" -> "5" [arrowhead="empty", arrowtail="none"];
"10" -> "6" [arrowhead="empty", arrowtail="none"];
}

Back-end

The back-end is more complicated. There are several steps to be taken here.

  1. Tree creation
  2. Instruction selection
  3. Register allocation
  4. Peep hole optimization

digraph codegen {
1 [label="IR-code"]

10 [label="irdag"]
20 [label="dagsplitter"]
30 [label="instruction selector"]
40 [label="register allocator"]

49 [label="assembly parser"]
50 [label="outstream"]
60 [label="object file"]
61 [label="text output"]
1 -> 10
10 -> 20 [label="Selection DAG"]
20 -> 30 [label="Selection Trees"]
30 -> 40 [label="frame"]
40 -> 50 [label="frame"]

49 -> 50
50 -> 60
50 -> 61
}

Code generator

Machine code generator. The architecture is provided when the generator is created.

digraph "classes_foo" {
charset="utf-8"
rankdir=BT
"0" [label="{CodeGenerator|arch\ldebug_db\linstruction_scheduler : InstructionScheduler\linstruction_selector : InstructionSelector1\llogger : RootLogger, NoneType\lregister_allocator : GraphColoringRegisterAllocator\lsgraph_builder : SelectionGraphBuilder\lverifier : Verifier\l|define_arguments_live()\lemit_frame_to_stream()\lgenerate()\lgenerate_function()\lselect_and_schedule()\l}", shape="record"];
}

Canonicalize

During this phase, the IR-code is made simpler. Also unsupported operations are rewritten into function calls. For example soft floating point is introduced here.

Tree building

From IR-code a tree is generated which can be used to select instructions.

The process of instruction selection is preceeded by the creation of a selection dag (directed acyclic graph). The dagger take ir-code as input and produces such a dag for instruction selection.

A DAG represents the logic (computation) of a single basic block.

Instruction selection

The instruction selection phase takes care of scheduling and instruction selection. The output of this phase is a one frame per function with a flat list of abstract machine instructions.

To select instruction, a tree rewrite system is used. This is also called bottom up rewrite generator (BURG). See pyburg.

Register allocation

Selected instructions use virtual registers and physical registers. Register allocation is the process of assigning a register to the virtual registers.

Some key concepts in the domain of register allocation are:

  • virtual register: A value which must be mapped to a physical register.
  • physical register: A real register
  • interference graph: A graph in which each node represents a register. An edge indicates that the two registers cannot have the same register assigned.
  • pre-colored register: A register that is already assigned a specific physical register.
  • coalescing: The process of merging two nodes in an interference graph which do not interfere and are connected by a move instruction.
  • spilling: Spilling is the process when no physical register can be found for a certain virtual register. Then this value will be placed in memory.
  • register class: Most CPU’s contain registers grouped into classes. For example, there may be registers for floating point, and registers for integer operations.
  • register alias: Some registers may alias to registers in another class. A good example are the x86 registers rax, eax, ax, al and ah.

Interference graph

Each instruction in the instruction list may use or define certain registers. A register is live between a use and define of a register. Registers that are live at the same point in the program interfere with each other. An interference graph is a graph in which each node represents a register and each edge represents interference between those two registers.

Graph coloring

In 1981 Chaitin presented the idea to use graph coloring for register allocation.

In a graph a node can be colored if it has less neighbours than possible colors. This is true because when each neighbour has a different color, there is still a valid color left for the node itself.

Given a graph, if a node can be colored, remove this node from the graph and put it on a stack. When added back to the graph, it can be given a color. Now repeat this process recursively with the remaining graph. When the graph is empty, place all nodes back from the stack one by one and assign each node a color when placed. Remember that when adding back, a color can be found, because this was the criteria during removal.

See: https://en.wikipedia.org/wiki/Chaitin%27s_algorithm

[Chaitin1982]

Coalescing

Coalescing is the process of merging two nodes in an interference graph. This means that two temporaries will be assigned to the same register. This is especially useful if the temporaries are used in move instructions, since when the source and the destination of a move instruction are the same register, the move can be deleted.

Coalescing a move instruction is easy when an interference graph is present. Two nodes that are used by a move instruction can be coalesced when they do not interfere.

However, if we coalesc all moves, the graph can become incolorable, and spilling has to be done. To prevent spilling, coalescing must be done conservatively.

A conservative approach is the following: if the merged node has fewer than K nodes of significant degree, then the nodes can be coalesced. The prove for this is that when all nodes that can be colored are removed and the merged node and its non-colorable neighbours remain, the merged node can be colored. This ensures that the coalescing of the node does not have a negative effect on the colorability of the graph.

[Briggs1994]

Spilling

Iterated register coalescing

Iterated register coalescing (IRC) is a combination of graph coloring, coalescing and spilling. The process consists of the following steps:

  • build an interference graph from the instruction list
  • remove trivial colorable nodes.
  • (optional) coalesc registers to remove redundant moves
  • (optional) spill registers
  • select registers

See: https://en.wikipedia.org/wiki/Register_allocation

[George1996]

Graph coloring with more register classes

Most instruction sets are not ideal, and hence simple graph coloring cannot be used. The algorithm can be modified to work with several register classes that possibly interfere.

[Runeson2003] [Smith2004]

Implementations

The following class can be used to perform register allocation.

class ppci.codegen.registerallocator.GraphColoringRegisterAllocator(arch: ppci.arch.arch.Architecture, instruction_selector, debug_db)

Target independent register allocator. Algorithm is iterated register coalescing by Appel and George. Also the pq-test algorithm for more register classes is added.

alloc_frame(frame)

Do iterated register allocation for a single frame.

This is the entry function for the register allocator and drives through all stages of the algorithm.

Parameters:frame – The frame to perform register allocation on.
coalesc()

Coalesc moves conservative.

This means, merge the variables of a move into one variable, and delete the move. But do this only when no spill will occur.

freeze()

Give up coalescing on some node, move it to the simplify list and freeze all moves associated with it.

is_colorable(node)

Helper function to determine whether a node is trivially colorable. This means: no matter the colors of the nodes neighbours, the node can be given a color.

In case of one register class, this is: n.degree < self.K

In case of more than one register class, somehow the worst case damage by all neighbours must be determined.

We do this now with the pq-test.

simplify()

Remove nodes from the graph

code emission

Code is emitted using the outputstream class. The assembler and compiler use this class to emit instructions to. The stream can output to object file or to a logger.

class ppci.binutils.outstream.OutputStream

Interface to generate code with. Contains the emit function to output instruction to the stream

Specification languages

Introduction

DRY

Do not repeat yourself (DRY). This is perhaps the most important idea to keep in mind when writing tools like assemblers, disassemblers, linkers, debuggers and compiler code generators. Writing these tools can be a repetitive and error prone task.

One way to achieve this is to to write a specification file for a specific processor and generate from this file the different tools. The goal of a machine description file is to describe a file and generate tools like assemblers, disassemblers, linkers, debuggers and simulators.

digraph x {
    1 [label="CPU specification"]
    2 [label="spec compiler"]
    10 [label="assembler"]
    11 [label="compiler back-end"]
    12 [label="simulator"]
    1 -> 2
    2 -> 10
    2 -> 11
    2 -> 12
}

Background

There are several existing languages to describe machines in a Domain Specific Language (DSL). Examples of these are:

  • Tablegen (llvm)
  • cgen (gnu)
  • LISA (Aachen)
  • nML (Berlin)
  • SLED (Specifying representations of machine instructions (norman ramsey and Mary F. Fernandez)) [sled]

Concepts to use in this language:

  • Single stream of instructions
  • State stored in memory
  • Pipelining
  • Instruction semantics

Optionally a description in terms of compiler code generation can be attached to this. But perhaps this clutters the description too much and we need to put it elsewhere.

The description language can help to expand these descriptions by expanding the permutations.

Example specifications

For a complete overview of ADL (Architecture Description Language) see [overview].

llvm
def IMUL64rr : RI<0xAF, MRMSrcReg, (outs GR64:$dst),
                                   (ins GR64:$src1, GR64:$src2),
                   "imul{q}\t{$src2, $dst|$dst, $src2}",
                   [(set GR64:$dst, EFLAGS,
                       (X86smul_flag GR64:$src1, GR64:$src2))],
                   IIC_IMUL64_RR>,
                TB;
LISA
<insn> BC
{
  <decode>
  {
    %ID: {0x7495, 0x0483}
    %cond_code: { %OPCODE1 & 0x7F }
    %dest_address: { %OPCODE2 }
  }
  <schedule>
  {
    BC1(PF, w:ebus_addr, w:pc) |
    BC2(PF, w:pc), BC3(IF) |
    BC4(ID) |
    <if> (condition[cond_code])
    {
      BC5(AC) |
      BC6(PF), BC7(ID), BC8(RE) |
      BC9(EX)
    }
    <else>
    {
      k:NOP(IF), BC10(AC, w:pc) |
      BC11(PF), BC12(ID), BC13(RE) |
      k:NOP(ID), BC14(EX) |
      k:NOP(ID), k:NOP(AC) |
      k:NOP(AC), k:NOP(RE) |
      k:NOP(RE), k:NOP(EX) |
      k:NOP(EX)
    }
  }
  <operate>
  {
    BC1.control: { ebus_addr = pc++; }
    BC2.control: { ir = mem[ebus_addr]; pc++ }
    BC10.control: { pc = (%OPCODE2) }
  }
}
SLED
patterns
  nullary is any of [ HALT NEG COM SHL SHR READ WRT NEWL NOOP TRA NOTR ],
    which is op = 0 & adr = { 0 to 10 }
constructors
  IMULb        Eaddr            is      (grp3.Eb;    Eaddr) & IMUL.AL.eAX
nML
type word = card(16)
type absa = card(9)
type disp = int(4)
type off = int(6)
mem PC[1,word]
mem R[16,word]
mem M[65536,word]
var L1[1,word]
var L2[1,word]
var L3[1,word]
mode register(i:card(4)) = R[i]
  syntax = format(”R%s”, i)
  image = format(”%4b”, i)
mode memory = ind | post | abs
mode ind(r:register, d:disp) = M[r+d]
  update = {}
  syntax = format(”@%s(%d)”, r.syntax, d)
  image = format(”0%4b%4b0”, r.image, d)
mode post(r:register, d:disp) = M[r+d]
  update = { r = r + 1; }
  syntax = format(”@%s++(%d)”, r.syntax, d)
  image = format(”0%4b%4b1”, r.image, d)
mode abs(a : absa) = M[a]
  update = {}
  syntax = format(”%d”, a)
  image = format(”1%9b”, a)
op instruction( i : instr )
  syntax = i.syntax
  image = i.image
  action = {
    PC = PC + 1;
    i.action;
  }
op instr = move | alu | jump
op move(lore:card(1), r:register, m:memory)
  syntax = format(”MOVE%d %s %s”, lore, r.syntax, m.syntax)
  image = format(”0%1b%4b%10b”, lore, r.image, m.image)
  action = {
    if ( lore ) then r = m;
    else m = r;
    endif;
    m.update;
  }
op alu(s1:register, s2:register, d:reg, a:aluop)
  syntax = format(”%s %s %s %s”, a.syntax, s1.syntax, s2.syntax, d.syntax)
  image = format(”10%4b%4b%4b%2b”, s1.image, s2.image, d.image, a.image)
  action = {
    L1 = s1; L2 = s2; a.action; d = L3;
  }
op jump(s1:register, s2:register, o:off)
  syntax = format(”JUMP %s %s %d”, s1.syntax, s2.syntax, o)
  image = format(”11%4b%4b%6b”, s1.image, s2.image, o)
  action = {
   if ( s1 >= S2 ) then PC = PC + o;
   endif;
  }
op aluop = and | add | sub | shift;
op and() syntax = ”and” image = ”00” action = { L3 = L1 & L2; }
op add() syntax = ”add” image = ”10” action = { L3 = L1 + L2; }
op sub() syntax = ”sub” image = ”01” action = { L3 = L1 - L2; }

Design

The following information must be captured in the specification file:

  • Assembly textual representation
  • Binary representation
  • Link relocations
  • Mapping from compiler back-end
  • Effects of instruction (semantics)
[sled]http://www.cs.tufts.edu/~nr/toolkit/
[overview]http://esl.cise.ufl.edu/Publications/iee05.pdf

Contributing

This section is intended for contributors of ppci.

Support

Ppci is still in development, and all help is welcome!

How to help

If you want to add some code to the project, the best way is to create a fork of the project, make your changes and create a pull request.

For a list of tasks, refer to the todo page. For help on getting started with development see the development page.

Report an issue

If you find a bug, you can file it here:

https://bitbucket.org/windel/ppci/issues

Development

This chapter descibes how to develop on ppci.

Communication

Join the #ppci irc channel on freenode!

Or visit the forum:

Source code

The sourcecode repository of the project is located at these locations:

To check out the latest code and work use the development version use these commands to checkout the source code and setup ppci such that you can use it without having to setup your python path.

$ mkdir HG
$ cd HG
$ hg clone https://bitbucket.org/windel/ppci
$ cd ppci
$ sudo python setup.py develop

Running the testsuite

To run the unit tests with the compiler, use pytest:

$ python -m pytest -v test/

Or use the unittest module:

$ python -m unittest discover -s test

In order to test ppci versus different versions of python, tox is used. To run tox, simply run in the root directory:

$ tox

Building the docs

The docs can be build locally by using sphinx. Sphinx can be invoked directly:

$ cd docs
$ sphinx-build -b html . build

Alternatively the tox docs environment can be used:

$ tox -e docs

Release procedure

  1. Determine the version numbers of this release and the next.

  2. Switch to the release branch and merge the default branch into the release branch.

    $ hg update release
    $ hg merge default
    $ hg commit
    
  3. Check the version number in ppci/__init__.py

  4. Make sure all tests pass and fix them if not.

    $ tox
    
  5. Tag this release with the intended version number and update to this tag.

    $ hg tag x.y.z
    $ hg update x.y.z
    
  6. Package and upload the python package. The following command creates a tar gz archive as well as a wheel package.

    $ python setup.py sdist bdist_wheel upload
    
  7. Switch back to the default branch and merge the release branch into the default branch.

    $ hg update default
    $ hg merge release
    $ hg commit
    
  8. Increase the version number in ppci/__init__.py.

Continuous integration

The compiler is tested for linux:

and for windows:

Code metrics

Code coverage is reported to the codecov service:

Other code metrics are listed at openhub:

Todo

Below is a list of features / tasks that need to be done.

  • Improve the debugger.
    • Add support for local variables.
  • Implement the disassembler further.
  • Implement Mac OSX support and add a mac 64-bit example project.
  • Add Windows support and add a windows 64-bit example project.
  • Improve the fuzzer tool that can generate random source code to stress the compiler.
  • Implement a fortran frontend. The ppci.lang.fortran module contains a start.
  • Implement a C frontend, The ppci.lang.c module contains an attempt.
  • Add a pascal frontend.
  • Add floating point support.
  • Add a peephole optimizer.
  • Add spilling to the register allocator.
  • Add a front-end for LLVM IR-code, this way, the front-end of LLVM can be used and the backend of ppci.
  • Add a backend for LLVM IR-code.
  • Add better support for harvard architecture cpu’s like avr, 8051 and PIC.

Faq

Why? WHY?!

There are several reasons:

  • it is possible!
  • this compiler is very portable due to python being portable.
  • writing a compiler is a very challenging task

Is this compiler slower than compilers written in C/C++?

Yes. Although a comparison is not yet done, this will be the case, due to the overhead and slower execution of python code.

Cool project, I want to contribute to this project, what can I do?

Great! If you want to add some code to the project, the best way is perhaps to send me a message, and create a fork of the project on bitbucket. If you are not sure where to begin, please contact me fist. For a list of tasks, refer to the todo page. For hints on development see the development page.

Changelog

Release 1.0 (Planned)

  • platform.python_compiler() returns ‘ppci 1.0’

Release 0.6 (Planned)

Release 0.5 (Upcoming)

  • Debug type information stored in better format
  • Expression evaluation in debugger
  • Global variables can be viewed
  • Improved support for different register classes

Release 0.4.0 (Apr 27, 2016)

  • Start with debugger and disassembler

Release 0.3.0 (Feb 23, 2016)

  • Added risc v architecture
  • Moved thumb into arm arch
  • msp430 improvements

Release 0.2.0 (Jan 23, 2016)

  • Added linker (ppci-ld.py) command
  • Rename buildfunctions to api
  • Rename target to arch

Release 0.1.0 (Dec 29, 2015)

  • Added x86_64 target.
  • Added msp430 target.

Release 0.0.5 (Mar 21, 2015)

  • Remove st-link and hence pyusb dependency.
  • Support for pypy3.

Release 0.0.4 (Feb 24, 2015)

Release 0.0.3 (Feb 17, 2015)

Release 0.0.2 (Nov 9, 2014)

Release 0.0.1 (Oct 10, 2014)

  • Initial release.