(version française)

INF564 - lab 8 - LTL Code Production, Linearization

The goal of this lab is to implement LTL code production for mini-C, then its translation to x86_64 assembly.

Provided Code

Archive to download: ltl-java.tar.gz

Once uncompressed (for instance with tar zxvf ltl-java.tar.gz), you get the following Java files:

LTL.java LTL Language (Location Transfer Language)
LTLinterp.java LTL Interpreter (to make tests)
X86_64.java x86-64 Assembly

These classes are complete and documented here. Take some time to read and understand all this.

1. LTL Code Production

The goal is to extend the mini-C compiler with a translation from ERTL to LTL, that is a value of type LTLfile. You can print such a value with method print from class LTL.

As usual, we strongly advise to proceed step by step, with systematic testing.

1.1. Building the Interference Graph

An interference graph has vertices that are registers and two kinds of edges (interference and preference). A Java class for this is as follows:
class Arcs {
  Set<Register> prefs = new HashSet<>();
  Set<Register> intfs = new HashSet<>();
}
class Interference {
  Map<Register, Arcs> graph;
}
Build such as graph from the liveness analysis, that is,
  Interference(Liveness lg) {
    ...
  }
Hints:
  1. make a first traversal of ERTL instructions (contained in the field instr of LiveInfo) to add a new preference edge for each instruction mov x y (with x and y distinct).
  2. make a second traversal of ERTL instructions to add the interference edges.
Be careful to build an undirected graph (for each edge x-y we also have an edge y-x).

To debug, you can print the interference graph with the following method:

  void print() {
    System.out.println("interference:");
    for (Register r: graph.keySet()) {
      Arcs a = graph.get(r);
      System.out.println("  " + r + " pref=" + a.prefs + " intf=" + a.intfs);
    }
  }
With a C program such as
int main() {
  return 42;
}
you should obtain something like
interference:
  #3 pref=[%r12] intf=[%rbx, %rax, #1, #2]
  %rbx pref=[#2] intf=[#3, %rax, %r12]
  %rax pref=[#1] intf=[#3, %rbx, %r12, #2]
  %r12 pref=[#3] intf=[%rbx, %rax, #2]
  #1 pref=[%rax] intf=[#3, #2]
  #2 pref=[%rbx] intf=[#3, %rax, %r12, #1]

1.2. Coloring the Interference Graph

We are now going to color the interference graph, that is to map each pseudo-register to a value of type Operand (a physical register or a stack slot). The coloring output is a map from pseudo-registers to colors and the number of stack slots required by the spilled registers.
class Coloring {
  Map<Register, Operand> colors = new HashMap<>();
  int nlocals = 0; // number of stack slots
  ...
}
The starting point is the interference graph, that is
  Coloring(Interference ig) {
    ...
  }

You do not have to implement George-Appel's algorithm presented in the lecture. You can go for something simpler, as follows:

  1. put all pseudo-registers in a todo set
  2. for each pseudo-register, maintain its available colors; initially, this is the difference between Register.allocatable and its interferences
  3. as long as todo is not empty

When selecting a register to color, you can favor

Spilled registers can be allocated linearly on the stack (i.e. each new spilled register picks the next available slot). Of course, we could do a much assignment (see the lecture).

To debug, one can print the coloring with the following method:

  void print() {
    System.out.println("coloring output:");
    for (Register r: colors.keySet()) {
      Operand o = colors.get(r);
      System.out.println("  " + r + " --> " + o);
    }
  }
With a C program such as
int main() {
  return 42;
}
you should obtain something like
coloring output:
  #3 --> %r12
  #1 --> %rax
  #2 --> %rbx

1.3. LTL Code Construction

To translate a function from ERTL to LTL, we need to
  1. perform liveness analysis (see lab 7)
  2. build the interference graph
  3. color it
  4. translate each ERTL instruction to one or several LTL instructions
This last step is very similar to the translation from RTL to ERTL (lab 7). You can use the visitor ERTLVisitor that is provided and implement something like
class ToLTL implements ERTLVisitor {
  private Coloring coloring; // coloring for the current function
  int size_locals; // size for the spilled registers
  LTLgraph graph;  // graph under construction
  ...
}

You can print the result with method print from class LTLfile. With a C program such as

int main() {
  return 42;
}
you should obtain something like
=== LTL ==================================================
main()
  entry : L5
  L5: goto L4
  L4: goto L3
  L3: goto L2
  L2: mov $42 %rax  --> L1
  L1: goto L10
  L10: goto L9
  L9: goto L8
  L8: goto L7
  L7: goto L6
  L6: return

1.4. Tests

Extend your compiler with a new command line option --interp-ltl to execute the LTL code with the provided class LTLinterp.

Then you can test as follows:

> ./run -i  "mon/chemin/vers/mini-c --interp-ltl"
Test de mon/chemin/vers/mini-c --interp-ltl

Interprétation
Execution normale
-----------------
...........................................................
Execution conduisant à un échec
-------------------------------
...
Compilation:
Compilation : 62/62 : 100%
Comportement du code : 62/62 : 100%

2. Assembly Code Production

The very last step is to turn LTL code into x86-64 assembly code, that is to linearize the LTL control-flow graph. The class X86_64 provides means to build x86-64 assembly code and then to print it in a file.

2.1 Translating a LTL Function

To translate the LTL control-flow graph to assembly, you can use the visitor provided with class LTL, as follows:
class Lin implements LTLVisitor {
  private LTLgraph cfg; // graph under translation
  private X86_64 asm; // assembly code under de construction
  private HashSet<Label> visited; // instructions already translated
  ...
The assembly code under construction is stored in asm. The table visited stores the instructions already translated. You can use the following method:
  private void lin(Label l) {
    if (visited.contains(l)) {
      asm.needLabel(l);
      asm.jmp(l.name);
    } else {
      visited.add(l);
      asm.label(l);
      cfg.graph.get(l).accept(this);
    }
  }
It produces code from a given label, if not already done, and a jump to that label otherwise. This code is mutually recursive with the various visit methods from class Lin that translate the various LTL instructions.

We translate a LTL function f as follows:

    cfg = f.body;
    asm.label(f.name);
    lin(f.entry);

2.2 Translating a LTL Program

To translate a LTL program, we proceed as follows: When this is done, the main class can print the assembly code into a file with method X86_64.printToFile.

With a C program such as

int main() {
  return 42;
}
you should obtain something like
        .text
	.globl main
main:
	movq $42, %rax
	ret
        .data

2.3 Final Tests

Test your compiler as follows:
> ./run -3  mon/chemin/vers/mini-c
Test de mon/chemin/vers/mini-c

Partie 3
Execution normale
-----------------
...........................................................
Execution conduisant à un échec
-------------------------------
../run: line 266: 12739 Segmentation fault      ./a.out > out
../run: line 266: 12751 Floating point exception./a.out > out
../run: line 266: 12763 Floating point exception./a.out > out

Compilation:
Compilation : 62/62 : 100%
Code produit : 62/62 : 100%
Comportement du code : 62/62 : 100%

back to the main page