Once uncompressed (for instance with tar zxvf ltl-ocaml.tar.gz), you get the following OCaml modules:
Ltltree | Location Transfer Language (LTL) |
Ltlinterp | LTL Interpreter (to make tests) |
X86_64 | x86-64 Assembly |
These modules are complete. Click on the module name to access its documentation. Take some time to read and understand all this.
As usual, we strongly advise to proceed step by step, with systematic testing.
type arcs = { prefs: Register.set; intfs: Register.set } type igraph = arcs Register.mapBuild such as graph from the liveness analysis, that is, implement a function
val make: live_info Label.map -> igraphHints:
To debug, you can print the interference graph with the following function:
let print ig = Register.M.iter (fun r arcs -> Format.printf "%s: prefs=@[%a@] intfs=@[%a@]@." (r :> string) Register.print_set arcs.prefs Register.print_set arcs.intfs) igWith a C program such as
int main() { return 42; }you should obtain something like
#1: prefs=%rax intfs=#2, #3 #2: prefs=%rbx intfs=#1, #3, %r12, %rax #3: prefs=%r12 intfs=#1, #2, %rax, %rbx %r12: prefs=#3 intfs=#2, %rax, %rbx %rax: prefs=#1 intfs=#2, #3, %r12, %rbx %rbx: prefs=#2 intfs=#3, %r12, %rax
type color = Ltltree.operand type coloring = color Register.mapImplement the coloring as a function with the following type:
val color: igraph -> coloring * intThe second output is the number of stack slots required by the spilled registers.
You do not have to implement George-Appel's algorithm presented in the lecture. You can go for something simpler, as follows:
When selecting a register to color, you can favor
To debug, one can print the coloring with the following function:
open Format let print_color fmt = function | Reg hr -> fprintf fmt "%a" Register.print hr | Spilled n -> fprintf fmt "stack %d" n let print cm = RM.iter (fun r cr -> printf "%a -> %a@\n" Register.print r print_color cr) cmWith a C program such as
int main() { return 42; }you should obtain something like
#1 -> %rax #2 -> %rbx #3 -> %r12
let instr c frame_size = function | Ertltree.Econst (n, r, l) -> Econst (n, lookup c r, l) ...where c is the coloring and frame_size is the size for the spilled registers.
The function lookup below maps a variable to its colors:
let lookup c r = if is_hw r then Reg r else M.find r c
You can print the LTL program with function Ltltree.print_file. 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
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%
let visited = Hashtbl.create 17 type instr = Code of X86_64.text | Label of Label.t let code = ref [] let emit l i = code := Code i :: Label l :: !code let emit_wl i = code := Code i :: !code let labels = Hashtbl.create 17 let need_label l = Hashtbl.add labels l ()The assembly code under construction is list !code (in reverse order). An instruction is added, together with a label, using function emit. The table visited stores the instructions already translated. The table labels stores the labels we need to keep in the final assembly code (jump targets).
Then you can implement two mutually recursive functions:
let rec lin g l = if not (Hashtbl.mem visited l) then begin Hashtbl.add visited l (); instr g l (Label.M.find l g) end else begin need_label l; emit_wl (jmp (l :> string)) end and instr g l = function | Econst (n, r, l1) -> emit l (movq (imm32 n) (operand r)); lin g l1 ...Function lin produces code from a given label, if not already done, and a jump to that label otherwise. Function instr produces the code from a given label and its corresponding instruction.
Function operand above, that you have to implement, translates a LTL operand with type Ltltree.operand to an x86-64 operand with type X86_64.operand. To translate a register with type Register.t to a register with type X86_64.register, a function X86_64.register64 is provided.
Once the LTL graph is fully translated (by calling lin on the function entry label), we can filter !code to get rid of labels that are not in table labels and then concatenate the filtered list with operator ++ from module X86_64.
With a C program such as
int main() { return 42; }you should obtain something like
.text .globl main main: movq $42, %rax ret .data
> ./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%