libqbe/lo.ml
2015-01-01 17:08:09 -05:00

368 lines
8.7 KiB
OCaml

type id = int
module ISet = Set.Make
(struct
type t = int
let compare = compare
end)
type unop = Not
type binop =
| Add | Sub
| Le | Ge | Lt | Gt | Eq | Ne
type phi = { pjmp: id; pvar: int }
type instr =
| INop
| ICon of int
| IUop of unop * id
| IBop of id * binop * id
| IBrz of id * id * id
| IJmp of id
| IPhi of phi list
(* Phi nodes must be at the join of branches
in the control flow graph, if n branches
join, the phi node must have n elements in
its list that indicate the value to merge
from each of the branches.
The id given in each of
*)
type prog = instr array
(* Here, we analyze a program backwards to
compute the liveness of all variables.
We assume that all phi nodes are placed
correctly.
*)
let liveness p =
(* The idea is now to reach a fixpoint
by applying the same backward liveness
propagation a sufficient number of
times.
The [changed] variable will tell us
when we reached the fixpoint, it is
reset to false at each iteration.
*)
let changed = ref true in
let liveout = Array.make (Array.length p) ISet.empty in
let setlive v l =
(* Extend the liveness of v to l. *)
if not (ISet.mem v liveout.(l)) then begin
changed := true;
liveout.(l) <- ISet.add v liveout.(l);
end in
let succs i =
(* Retreive the successor nodes of i. *)
if i = Array.length p -1 then [] else
match p.(i) with
| IBrz (_, i1, i2) -> [i1; i2]
| IJmp i1 -> [i1]
| _ -> [i+1] in
let gen i = ISet.of_list
(* Get the Gen set of i. *)
begin match p.(i) with
| IUop (_, i1) -> [i1]
| IBop (i1, _, i2) -> [i1; i2]
| IPhi l ->
List.iter (fun {pjmp; pvar} ->
setlive pvar pjmp
) l; []
| _ -> []
end in
let livein i =
(* Get the live In set of i. *)
let s = liveout.(i) in
let s = ISet.union s (gen i) in
ISet.remove i s in
(* The fixpoint computation. *)
while !changed do
changed := false;
for i = Array.length p -1 downto 0 do
(* Collect live Ins of all successor blocks. *)
let live = List.fold_left (fun live i' ->
ISet.union live (livein i')
) ISet.empty (succs i) in
ISet.iter (fun i' ->
setlive i' i
) live
done
done;
liveout
type reginfo =
{ mutable rreg: int
; mutable rspill: int option
; mutable rhint: int
}
let regalloc nr p l =
let regs = Array.init (Array.length p)
(fun _ ->
{ rreg = -1
; rspill = None
; rhint = -1
}) in
let ( |> ) a b = if a = -1 then b else a in
(* Number of spilled registers. *)
let spill = ref 0 in
let rspill i =
if regs.(i).rspill = None then begin
regs.(i).rspill <- Some !spill;
incr spill;
end in
(* Associative list binding irrefs to registers,
it is ordered by freshness. *)
let used = ref [] in
let free = ref (
let rec m i = if i = nr then [] else i :: m (i+1)
in m 0
) in
let alloc hint i =
let r, fl =
let l, fl = List.partition ((=) hint) !free in
if l <> [] then (hint, fl) else
match !free with
| r :: fl -> (r, fl)
| [] ->
(* No more free registers, we need to spill. *)
let rec g = function
| [] -> assert false
| [r,i'] -> rspill i'; (r, [])
| x :: us ->
let (r, us) = g us in
(r, x :: us) in
let r, us = g !used in
used := us;
r, [] in
free := fl;
used := (r, i) :: !used;
r in
for i = 0 to Array.length p -1 do
(* Forget about all bindings that are not live
at the end of the instruction. *)
let used', free' = List.partition
(fun (_, i') -> ISet.mem i' l.(i)) !used in
used := used';
free := List.map fst free' @ !free;
(* Bind a register to the current instruction
if its result is not discarded. *)
if ISet.mem i l.(i) then begin
match p.(i) with
| ICon _ | IBrz _ | IJmp _ | INop -> ()
| IPhi l ->
(* Try to ensure that variables merged by a phi
use the same register. *)
let f r {pvar;_} = regs.(pvar).rreg |> r in
let r = List.fold_left f (-1) l in
let r =
let h = regs.(i).rhint in
if r = -1 then alloc h i else r in
List.iter (fun {pvar;_} ->
regs.(pvar).rhint <- r
) l;
regs.(i).rreg <- r
| IUop (_, i')
| IBop (i', _, _) ->
let h =
regs.(i).rhint |>
regs.(i').rreg |>
regs.(i').rhint in
regs.(i).rreg <- alloc h i
end;
done;
(regs, !spill)
(* Testing. *)
let parse src =
let blocks = Hashtbl.create 31 in
let rec addlbl idx l =
let l = String.trim l in
try
let il = String.index l ':' in
let lbl = String.sub l 0 il in
Hashtbl.add blocks lbl idx;
let l =
String.sub l (il+1)
(String.length l -(il+1)) in
addlbl idx l
with Not_found -> l ^ " " in
let src = List.mapi addlbl src in
let p = Array.make (List.length src) INop in
List.iteri (fun idx l ->
let fail s =
failwith
(Printf.sprintf "line %d: %s" (idx+1) s) in
let tok =
let p = ref 0 in fun () ->
try
while l.[!p] = ' ' do incr p done;
let p0 = !p in
while l.[!p] <> ' ' do incr p done;
String.sub l p0 (!p - p0)
with _ -> fail "token expected" in
let id () =
let v = tok () in
try Hashtbl.find blocks v
with _ -> fail ("unknown variable " ^ v) in
let instr =
if l = " " then INop else
let bop o =
let i1 = id () in
let i2 = id () in
IBop (i1, o, i2) in
match tok () with
| "con" -> ICon (int_of_string (tok ()))
| "not" -> IUop (Not, id ())
| "add" -> bop Add
| "sub" -> bop Sub
| "cle" -> bop Le
| "cge" -> bop Ge
| "clt" -> bop Lt
| "cgt" -> bop Gt
| "ceq" -> bop Eq
| "cne" -> bop Ne
| "phi" ->
let exp t =
let t' = tok () in
if t' <> t then
fail ("unexpected " ^ t') in
let rec f () =
match tok () with
| "[" ->
let pjmp = id () in
let pvar = id () in
exp "]";
{pjmp; pvar} :: f ()
| "." -> []
| t -> fail ("unexpected " ^ t) in
IPhi (f ())
| "brz" ->
let v = id () in
let bz = id () in
let bn = id () in
IBrz (v, bz, bn)
| "jmp" -> IJmp (id ())
| i -> fail ("invalid " ^ i) in
p.(idx) <- instr
) src;
p
let t_pow = parse
[ "k0: con 0"
; "ni: con 1234"
; "k1: con 1"
; "n0: phi [ jmp n1 ] [ k1 ni ] ."
; "f1: phi [ jmp f2 ] [ k1 k1 ] ."
; "n1: sub n0 k1"
; "f2: add f1 f1"
; "jmp: brz n1 end n0"
; "end:"
]
(*
The following program has irreducible
control-flow. The control flow is
pictured below.
+--b1 <- defs r0, r1
| |
b2 b3
| |
\ b4<-+ <- uses r0
\ | |
+--b5 | <- uses r1
| | |
b7 b6--+
A simple implementation (that works for
non-irreducible control flows) proceeds
backwards, it would successfully make r1
live in b2 and b3 but r0 would fail to be
live in b2. It would become live for the
loop b4-b5-b6 when reaching the loop header
b4, but the simple algorithm would not
propagate back to b2.
*)
let t_irred = parse
[ "k0: con 0"
; "r0: con 1"
; "r1: con 2"
; "b1: brz k0 b2 b3"
; "b2: jmp b5"
; "b3:"
; "b4: add r0 k0"
; "b50: add r1 k0"
; "b5: brz k0 b6 b7"
; "b6: jmp b4"
; "b7:"
]
let _ =
let p = t_pow in
let open Printf in
printf "\n** Liveness analysis:\n";
let l = liveness p in
for i = 0 to Array.length p -1 do
printf "%04d:" i;
ISet.iter (printf " %04d") l.(i);
printf "\n";
done;
printf "\n** Register allocation:\n";
let regs = [| "rax"; "rbx"; "rcx" |] in
let r, s = regalloc (Array.length regs) p l in
if s <> 0 then printf "!! Needs spills !!\n";
let bop_str = function
| Add -> "add" | Sub -> "sub"
| Le -> "cle" | Ge -> "cge" | Lt -> "clt" | Gt -> "cgt" | Eq -> "ceq" | Ne -> "cne" in
for i = 0 to Array.length p -1 do
let reg i =
if r.(i).rreg = -1 then sprintf "%03d" i else regs.(r.(i).rreg) in
if r.(i).rreg = -1
then printf "%03d: " i
else printf "%s = " (reg i);
begin match p.(i) with
| ICon k -> printf "%d" k
| INop -> ()
| IUop (Not, i') -> printf "not %s" (reg i')
| IBop (i1, o, i2) ->
printf "%s %s %s" (bop_str o) (reg i1) (reg i2)
| IBrz (i1, i2, i3) ->
printf "brz %s %03d %03d" (reg i1) i2 i3
| IJmp i' ->
printf "jmp %s" (reg i')
| IPhi l ->
printf "phi ";
List.iter (fun {pjmp; pvar} ->
printf "[ %d %s ] " pjmp (reg pvar)
) l
end;
printf "\n"
done