(*
   regalloc.ml
   15-411
   by Arthur O'Dwyer
*)
(* @version $Id: regalloc.ml,v 1.4 2004/11/01 09:21:32 ajo Exp $ *)

(*
   The register-allocation function maps a program and a positive
   integer number of registers onto a /new/ program (with spill code
   inserted), a nonnegative integer number of stackslots that need
   to be set up by the standard prelude, and a synthesized function 
   mapping individual temps onto their register or stackslot assignments.

   This module implements Chaitin's algorithm for register allocation
   and assignment, as described in class.

   regalloc : instr list -> int -> (instr list * int * (temp -> regalloc_t))
*)

module A = Assem
module E = Errormsg
module P = Printf
open Types

exception RegallocBug of string

let print_instr i =
  let temp2string t = Printf.sprintf "t%d" t
  in print_string(A.format temp2string i)
let print_instrlist prog = List.iter print_instr prog

let print_live_1 livelst =
  let eachitem res t = res ^ (match t with
    | TEMP t -> Printf.sprintf " t%d" t
    | REGISTER r -> Printf.sprintf " r%d" r
    | _ -> " whoops!")
  in print_string ((List.fold_left eachitem "" livelst) ^ "\n")
let print_live_in lstlst = List.iter print_live_1 lstlst


(*
   An |igraph| is a mapping from temps to their |inode| data and
   a list of their neighbors in the interference graph.  Thus, all
   entries in the table are of the form |(REGISTER r, ...)| or else
   of the form |(TEMP t, ...)|.  And the |REGISTER| entries are just
   in there for completeness; they are pre-colored as |I_REGISTER|
   and do not participate in the clever parts of Chaitin's algorithm.

     This means that we can iterate over all the relevant entries
   in the graph using |iota numtemps| or else using
   |for t=1 to numtemps do|.

     The second field of the data record, the |boolean| value, is
   |true| if this node is a member of the graph and |false| otherwise.
   This is so we don't have to actually destroy the whole graph during
   the "destroy the graph" phase of Chaitin's algorithm; we just mark
   deleted nodes with |false|, and remember not to count them as
   neighbors of any remaining undeleted nodes.
*)
type inode = I_CAND of register
           | I_CAND_REG of register
           | I_REGISTER of register
           | I_STACKSLOT of stackslot
type halfgraph = (regalloc_t,
                   (bool * inode * (regalloc_t list))
                 ) Hashtbl.t
type igraph = int * halfgraph

(*
   chaitin_init : (regalloc_t list list * int numregs) -> (igraph)
   chaitin_iter : (igraph ref * int numregs) -> (int list temps_to_spill)
   chaitin_final : (igraph ref) -> (temp -> reg_or_slot)
*)


let print_igraph mygraph =
  let (numtemps, grph) = !mygraph in
  Printf.printf "-------------------START_GRAPH\n";
  for t = 1 to numtemps do
    let (b,ra,nb) = Hashtbl.find grph (TEMP t) in
    Printf.printf "%s\t" (if b then "true" else "false");
    (match ra with
    | I_CAND r -> Printf.printf "I_CAND %d\t" r
    | I_CAND_REG r -> Printf.printf "I_CAND_REG %d\t" r
    | I_REGISTER r -> Printf.printf "I_REGISTER %d\t" r
    | I_STACKSLOT s -> Printf.printf "I_STACKSLOT %d\t" s
    );
    Printf.printf "with neighbors ";
    for i=0 to (List.length nb)-1 do
      match List.nth nb i with
      | REGISTER r -> Printf.printf "r%d " r
      | STACKSLOT s -> Printf.printf "s%d " s
      | TEMP t -> Printf.printf "t%d " t
    done;
    Printf.printf "\n";
  done;
  print_string "-------------------END_GRAPH\n"


(*
   The |iota| function returns the list $1..n$ for any given $n>=1$.
*)
let iota x =
  let rec iota' x = if (x <= 0) then [] else (x :: iota'(x-1)) in
  List.rev (iota' x)


(*
   A couple of functions to deal with sets of things: |setplus|
   and |setminus|.  The arguments to these functions must be
   lists, each of which contains no duplicate items.  The lists
   returned by these functions satisfy the same constraint.

   type 'a set = ('a -> int) * BitList
   set_of_list : 'a list -> ('a -> int) * (int -> 'a) -> int -> 'a set
   set_to_list : 'a set -> 'a list
   setplus : ('a set * 'a set) -> 'a set
   setminus : ('a set * 'a set) -> 'a set
   list_setplus : ('a list * 'a list) -> 'a list
   list_setminus : ('a list * 'a list) -> 'a list
*)
type 'a set = ('a -> int) * (int -> 'a) * Bitlist.bitList_t

let set_of_list (a2i, i2a) len ali =
  let bitli = Bitlist.newBitList len in
  List.iter (fun a -> Bitlist.setBit bitli (a2i a)) ali;
  (a2i, i2a, bitli)
let set_to_list (a2i, i2a, bitli) =
  let rc = ref [] in
  Bitlist.iterTrue (fun i -> rc := (i2a i)::!rc) bitli;
  !rc
let setplus ((a2i, i2a, bitli1), (_, _, bitli2)) =
  let bitli3 = Bitlist.orBLnew bitli1 bitli2 in
  (a2i, i2a, bitli3)
let setminus ((a2i, i2a, bitli1), (_, _, bitli2)) =
  let bitli3 = Bitlist.notBLnew bitli2 in
  Bitlist.andBLto2 bitli1 bitli3;
  (a2i, i2a, bitli3)
let sets_equal ((_, _, bitli1), (_, _, bitli2)) =
  Bitlist.isEqual bitli1 bitli2

let rat2i = function
  | REGISTER r -> r
  | TEMP t -> (t+8)
let i2rat = function
  | i when i<8 -> REGISTER i
  | i -> TEMP (i-8)
let ratset = set_of_list (rat2i, i2rat)

let rec list_setplus = function
  | (a, []) -> a
  | ([], b) -> b
  | (h::t, b) -> if (List.mem h b) then list_setplus(t,b)
                 else list_setplus(t,h::b)
let rec list_setminus = function
  | (a, []) -> a
  | ([], b) -> []
  | (h::t, b) -> if (List.mem h b) then list_setminus(t,b)
                 else h::list_setminus(t,b)

(*
   Remove duplicates from a list.
*)
let list_set_of_list ali =
  let ali = List.fast_sort compare ali in
  let rec help = function
    | [] -> [] | [a] -> [a]
    | a::b::t -> if (a=b) then help (b::t) else a::(help (b::t))
  in help ali

let use_in = function
  | MOVE(_,s,d) -> [s]
  | OPER(_,s,d) | LIVEOPER(_,s,d) -> s
  | JUMP _ | LABEL _ | COMMENT _ -> []
and def_in = function
  | MOVE(_,s,d) -> [d]
  | OPER(_,s,d) | LIVEOPER(_,s,d) -> d
  | JUMP _ | LABEL _ | COMMENT _ -> []

(*
   The function |get_live_in| takes a list of instructions
   of length $N$ and returns a list of |livein| sets of length
   $N+1$.

     We ought to also remove any "dead code"---|MOVE| or |OPER| 
   instructions whose destination sets are not live.  Those instructions 
   we just throw away.  We do /not/ throw away |LIVEOPER| instructions, 
   which may have side effects not reflected by their "use" or "def" 
   lists.

   get_live_in : instr list ref -> livein list
*)
let get_live_in prog ntemps =
  let plen = List.length !prog in
  let jumps_to_memo = Hashtbl.create 100 in
  let jumps_to marker = (
    let rec help n = function
      | [] -> []
      | (JUMP _ as j)::t ->
         if (A.marker j = marker) then n::help (n+1) t
         else help (n+1) t
      | _::t -> help (n+1) t
    in try (
      Hashtbl.find jumps_to_memo marker
    ) with Not_found -> (
      let result = help 0 !prog in
      Hashtbl.add jumps_to_memo marker result;
      result
    )
  ) in
  let find_label_memo = Hashtbl.create 100 in
  let find_label marker = (
    let rec help n = function
      | [] -> plen
      | (LABEL _ as k)::t ->
         if (A.marker k = marker) then n
         else help (n+1) t
      | _::t -> help (n+1) t
    in try (
      Hashtbl.find find_label_memo marker
    ) with Not_found -> (
      let result = help 0 !prog in
      Hashtbl.add find_label_memo marker result;
      result
    )
  ) in
  let pred i = (try (
    match (List.nth !prog i) with
      | LABEL _ as k when i=0 -> (jumps_to (Assem.marker k))
      | LABEL _ as k -> (i-1)::(jumps_to (Assem.marker k))
      | _ when i=0 -> []
      | _ -> [i-1]
    ) with _->[i-1]
  ) in
  let succ i = (try (
    match (List.nth !prog i) with
      | JUMP _ as j -> find_label (Assem.marker j) :: [i+1]
      | _ -> [i+1]
    ) with _ -> []
  ) in
  let usex = Array.init (plen+1) (fun i -> ratset ntemps
      (try use_in(List.nth !prog i) with _->[REGISTER 1]))
  and defx = Array.init (plen+1) (fun i -> ratset ntemps
      (try def_in(List.nth !prog i) with _->[]))
  and inx: regalloc_t set array = Array.init (plen+1) (fun i -> ratset 
ntemps
      (try use_in(List.nth !prog i) with _->[REGISTER 1]))
  and outx: regalloc_t set array = Array.init (plen+1) (fun i -> ratset 
ntemps [])
  and notdone = ref true in
  begin
    while !notdone do
      notdone := false;
      ignore(E.debugf "GO LIVEIN! plen=%d\n" plen);
      for i=plen downto 0 do
        let help s = outx.(i) <- setplus(outx.(i), inx.(s)) in
        List.iter help (succ i)
      done;
      for i=plen downto 0 do
        let new_inxi = setplus(setminus(outx.(i), defx.(i)), inx.(i)) in
        if not (sets_equal(new_inxi, inx.(i))) then notdone := true;
        inx.(i) <- new_inxi;
      done;
(*
      if (!E.debug) then (
        List.iter (function i ->
          Printf.printf "%d)  " (i-1);
          (try print_instr (List.nth !prog (i-1))
          with _ -> (print_string "---\n"));
          print_string "usex: "; print_live_1 usex.(i-1);
          print_string "defx: "; print_live_1 defx.(i-1);
          print_string "inx: "; print_live_1 inx.(i-1);
          print_string "outx: "; print_live_1 outx.(i-1)
        ) (iota (plen+1))
      )
*)
    done;
    List.map set_to_list (Array.to_list inx)
  end


(*
   The function |get_all_temps| takes a list of instructions
   and returns a set of all the temps mentioned in the source
   and destination lists for that program.

   get_all_temps : instr list -> temp list
   count_temps : instr list -> int
*)
let get_all_temps prog =
  let get_all_temps_helper ins set =
    match ins with
    | OPER(_,s,d) | LIVEOPER(_,s,d) -> (set @ d @ s)
    | MOVE(_,s,d) -> s::d::set
    | JUMP _ | LABEL _ | COMMENT _ -> set
  in
    List.fold_right get_all_temps_helper prog []
let count_temps prog =
  let rec get_max_temp = function
  | [] -> 0
  | TEMP(h)::t -> max h (get_max_temp t)
  | _::t -> get_max_temp t
  in
    get_max_temp (get_all_temps prog)


let chaitin_init initial_call mygraph
                 (interflist, numregs, numtemps, progr) =
  let proga = Array.of_list !progr in
  let rec populate_from n = function
    | [] -> (
        for i=1 to numtemps do
          let (b, node, neighb) = Hashtbl.find (snd !mygraph) (TEMP i) in
          let neighb = list_set_of_list neighb in
          Hashtbl.replace (snd !mygraph) (TEMP i) (b, node, neighb);
        done;
        for i=1 to numregs do
          let (b, node, neighb) = Hashtbl.find (snd !mygraph) (REGISTER i) in
          let neighb = list_set_of_list neighb in
          Hashtbl.replace (snd !mygraph) (REGISTER i) (b, node, neighb);
        done
      )
    | h::t -> begin (try
        populate_helper proga.(n) (list_setplus(h, def_in proga.(n)))
        with _ -> populate_helper (LIVEOPER("",[],[])) h);
        populate_from (n+1) t
      end
  and populate_helper ins lst = match lst with
    | [] -> ()
    | h::t -> begin
        populate_helper_helper ins h t;
        populate_helper ins t
      end
  and populate_helper_helper ins ra lst = try match lst with
    | [] -> ()
    | h::t -> (
      (match ins with
      | MOVE(_,s,d) when (s=ra) && (d=h) -> ()
      | MOVE(_,s,d) when (s=h) && (d=ra) -> ()
      | MOVE(_,s,d) when (s=d) -> ()
      | _ -> (
          let (rab, ranode, raneighbors) = Hashtbl.find (snd !mygraph) ra
          and (hb, hnode, hneighbors) = Hashtbl.find (snd !mygraph) h in
          let raneighbors = h::raneighbors
          and hneighbors = ra::hneighbors in
          Hashtbl.replace (snd !mygraph) h (hb, hnode, hneighbors);
          Hashtbl.replace (snd !mygraph) ra (rab, ranode, raneighbors)
        )
      );
      populate_helper_helper ins ra t
    )
    with
    | Not_found -> raise (RegallocBug "population_h_h Not_found");
  in
  let initialize mygraph =
    let allregs = iota numregs in
    begin
      Hashtbl.clear (snd !mygraph);
      for r = 1 to numregs do
        Hashtbl.add (snd !mygraph) (REGISTER r) (true, (I_REGISTER r),
          List.map (fun x -> REGISTER x) (list_setminus(allregs, [r])))
      done;
      for t = 1 to numtemps do
        Hashtbl.add (snd !mygraph) (TEMP t) (true, I_CAND 0, [])
      done
    end
  in begin
    if (initial_call) then begin
      initialize mygraph
    end;
    populate_from 0 interflist;
    mygraph := (numtemps, snd !mygraph)
  end

let chaitin_final mygraph =
  (fun t -> try match (Hashtbl.find (snd !mygraph) (TEMP t)) with
            | (_, I_STACKSLOT s, _) -> STACKSLOT s
            | (_, I_CAND r, _) -> if (r>0) then REGISTER r
                                  else raise (RegallocBug
                     (Printf.sprintf "CAND Temp %d was not assigned!" t))
            | (_, I_CAND_REG r, _) -> if (r>0) then REGISTER r
                                  else raise (RegallocBug
                     (Printf.sprintf "CAND_REG Temp %d was not assigned!" t))
            | _ -> raise (RegallocBug
                     (Printf.sprintf "ELSE Temp %d was not assigned!" t))
            with Not_found -> raise (RegallocBug
                     (Printf.sprintf "NOTFOUND Temp %d was not assigned!" t))
  )


exception RemovedAllNodes
exception SpillRandomTemp of regalloc_t

let chaitin_iter(mygraph, numregs) =
  let mystack: temp list ref = ref []
  and (numtemps,grph) = !mygraph
  and temps_to_spill = ref [] in
  let remove_a_graph_node() =
    begin
      (*
         If there exists a node of degree $<= numregs$, remove it.
         Otherwise, remove a node at random.  If no nodes are left
         at all, then raise the exception |RemovedAllNodes|.
      *)
      let is_undeleted t =
        let (b,_,_) = Hashtbl.find grph t in (b)
      and is_nonstack t =
        let (_,ra,_) = Hashtbl.find grph t in
          match ra with I_STACKSLOT _ -> false | _ -> true
      in
      let has_degree_less_than k t =
        let (_,_,nb) = Hashtbl.find grph t in
        let real_nb = List.filter is_undeleted nb in
        let nonstack_nb = List.filter is_nonstack nb in
          (List.length nb < k)
      in
      let poss = List.map (fun x->TEMP x) (List.rev (iota numtemps)) in
      let good = List.filter is_undeleted poss in
      let better = List.filter (has_degree_less_than numregs) good in
      let deTemp = function TEMP x->x | _->raise (RegallocBug "detemp") in
      let to_remove =
        match better with
        | h::t -> ignore(E.debugf "Better %d.\n" (deTemp h)); h
        | [] -> match good with
                | h::t -> ignore(E.debugf "Good %d.\n" (deTemp h)); h
                | [] -> raise RemovedAllNodes
      in begin
        let (x,y,z) = Hashtbl.find grph to_remove in
        Hashtbl.replace grph to_remove (false,y,z);
        mystack := (deTemp to_remove) :: !mystack
      end
    end in
  let rec replace_nodes (lst: temp list) = match lst with
    | [] -> !temps_to_spill
    | h::t -> try
      ignore(E.debugf "Trying to color t%d...\n" h);
      (*
         Color this node, or else add it to the list of temps to spill.
      *)
      let rec get_nb_regset nblist succ = match nblist with
        | [] -> succ
        | h::t ->
          let (exists,ra,_) = Hashtbl.find grph h in begin
            if (not exists) then get_nb_regset t succ
            else match ra with
                 | I_REGISTER(r) -> get_nb_regset t (list_setplus(succ, [r]))
                 | I_CAND_REG(r) -> get_nb_regset t (list_setplus(succ, [r]))
                 | I_CAND(r) -> get_nb_regset t (list_setplus(succ, [r]))
                 | _ -> get_nb_regset t succ
          end
      in
      let th = TEMP(h) in
      let (_,hra,hnb) = Hashtbl.find grph th in
      begin
        match hra with
        | I_CAND _ -> begin
            let nb_regset = get_nb_regset hnb [] in
            let okay_colors = List.filter
              (fun x -> not (List.mem x nb_regset)) (iota numregs) in
            match okay_colors with
            | [] -> (
                (* Spill this temp, |h|. *)
                temps_to_spill := h :: !temps_to_spill;
                Hashtbl.replace grph th (true, I_STACKSLOT 0, hnb)
              )
            | newcol::_ ->
                Hashtbl.replace grph th (true, I_CAND newcol, hnb)
          end
        | I_CAND_REG _ -> begin
            let nb_regset = get_nb_regset hnb [] in
            let okay_colors = List.filter
              (fun x -> not (List.mem x nb_regset)) (iota numregs) in
            match okay_colors with
            | [] -> begin
                (*
                   This is a tricky part of Chaitin's algorithm.  We have
                   here a temp that has been marked as |I_CAND_REG|, which
                   means it definitely must go in a register.  But we have
                   colored ourselves into a corner here!  We can't spill
                   this register; we must spill another one.

                     What I've done here is to pick a random spillable
                   register to spill.  That's very icky, and I'm not
                   entirely convinced it will always work, either.
                *)
                let is_spillable t =
                  match (Hashtbl.find grph t) with
                  | (_,I_CAND _,_) -> true
                  | _ -> false
                in
                let poss = List.map (fun x->TEMP x) (iota numtemps) in
                let good = List.filter is_spillable poss in
                match good with
                | h::t -> raise (SpillRandomTemp h)
                | [] -> raise (RegallocBug "We have hit a serious \
                        error, which is probably a bug.\nThe graph \
                        is completely full of I_CAND_REG nodes, and \
                        it's still uncolorable!\nThere is nothing I \
                        can do.  I'm going to crash now.")
              end
          | newcol::_ ->
              Hashtbl.replace grph th (true, I_CAND_REG newcol, hnb)
        end
        | I_REGISTER _
        | I_STACKSLOT _ -> (* do nothing but add the node to the graph *)
          Hashtbl.replace grph th (true, hra, hnb)
      end;
      replace_nodes t
    with SpillRandomTemp (TEMP t) -> (t :: !temps_to_spill)
  in
  begin
    (* Initialize every candidate temp to being-in-the-graph. *)
    for t = 1 to numtemps do try
        let (_,ra,neighbors) = Hashtbl.find grph (TEMP t) in
        Hashtbl.replace grph (TEMP t) (true,ra,neighbors)
      with Not_found -> raise (RegallocBug (Printf.sprintf
        "numtemps foo %d" t));
    done;
    try
      while true do
        remove_a_graph_node()
      done;
      raise (RegallocBug "-9999") (* never reached *)
    with RemovedAllNodes -> replace_nodes(!mystack)
  end

let rec trim_extra_moves alloc_fn instrlst = begin
  match instrlst with
  | [] -> []
  | (MOVE(_,TEMP s,TEMP d) as h)::t -> begin
      let s_assignment = alloc_fn(s)
      and d_assignment = alloc_fn(d) in
      if (s_assignment = d_assignment) then
        trim_extra_moves alloc_fn t
      else
        h :: (trim_extra_moves alloc_fn t)
    end
  | (MOVE(_,TEMP s,REGISTER d) as h)::t -> begin
      let s_assignment = alloc_fn(s) in
      if (s_assignment = REGISTER d) then
        trim_extra_moves alloc_fn t
      else
        h :: (trim_extra_moves alloc_fn t)
    end
  | (MOVE(_,REGISTER s,TEMP d) as h)::t -> begin
      let d_assignment = alloc_fn(d) in
      if (REGISTER s = d_assignment) then
        trim_extra_moves alloc_fn t
      else
        h :: (trim_extra_moves alloc_fn t)
    end
  | (_ as h)::t -> h :: (trim_extra_moves alloc_fn t)
  end

let regalloc (numregs, stackstart) prog =
  let maxtemp = ref (count_temps prog) in
  let get_new_temp() = begin maxtemp := !maxtemp+1; !maxtemp end in
  let maxslot = ref stackstart in
  let get_new_stackslot() = begin maxslot := !maxslot+1; !maxslot end in
  let curprog = ref prog
  and mygraph = ref (0, Hashtbl.create 0) in
  let rec rewrite_program(prog, to_spill) = begin
    let tempReplace(what,how) lst =
      let repl x = if (x = what) then how else x
      in List.map repl lst
    in
    match prog with
    | [] -> []
    | OPER(s,srcl,dstl) :: t -> begin
        if (List.mem to_spill srcl) or (List.mem to_spill dstl) then
          let tmp = TEMP(get_new_temp()) in
          let result = ref [] in
          mygraph := (1+fst !mygraph, snd !mygraph);
          Hashtbl.replace (snd !mygraph) tmp (true, I_CAND_REG 0, []);
          if (List.mem to_spill dstl) then begin
            result := MOVE("movl\t's0, 'd0", tmp, to_spill) :: !result
          end;
          result := OPER(s, tempReplace(to_spill, tmp) srcl,
                            tempReplace(to_spill, tmp) dstl) :: !result;
          if (List.mem to_spill srcl) then begin
            result := MOVE("movl\t's0, 'd0", to_spill, tmp) :: !result
          end;
          !result @ rewrite_program(t, to_spill)
        else
          OPER(s,srcl,dstl) :: rewrite_program(t, to_spill)
      end
    | LIVEOPER(s,srcl,dstl) :: t -> begin
        if (List.mem to_spill srcl) or (List.mem to_spill dstl) then
          let tmp = TEMP(get_new_temp()) in
          let result = ref [] in
          mygraph := (1+fst !mygraph, snd !mygraph);
          Hashtbl.replace (snd !mygraph) tmp (true, I_CAND_REG 0, []);
          if (List.mem to_spill dstl) then begin
            result := MOVE("movl\t's0, 'd0", tmp, to_spill) :: !result
          end;
          result := LIVEOPER(s, tempReplace(to_spill, tmp) srcl,
                            tempReplace(to_spill, tmp) dstl) :: !result;
          if (List.mem to_spill srcl) then begin
            result := MOVE("movl\t's0, 'd0", to_spill, tmp) :: !result
          end;
          !result @ rewrite_program(t, to_spill)
        else
          LIVEOPER(s,srcl,dstl) :: rewrite_program(t, to_spill)
      end
    | MOVE(s,src,dst) :: t -> begin
        if (src = to_spill) && (dst = to_spill) then begin
          (* Move from here to itself?  Get rid of that! *)
          rewrite_program(t, to_spill)
        end
        else if (src = to_spill) then begin
          let tmp = TEMP(get_new_temp()) in
          mygraph := (1+fst !mygraph, snd !mygraph);
          Hashtbl.replace (snd !mygraph) tmp (true, I_CAND_REG 0, []);
            MOVE("movl\t's0, 'd0", src, tmp) ::
            MOVE(s, tmp, dst) ::
            rewrite_program(t, to_spill)
        end
        else if (dst = to_spill) then begin
          let tmp = TEMP(get_new_temp()) in
          mygraph := (1+fst !mygraph, snd !mygraph);
          Hashtbl.replace (snd !mygraph) tmp (true, I_CAND_REG 0, []);
            MOVE(s, src, tmp) ::
            MOVE("movl\t's0, 'd0", tmp, dst) ::
            rewrite_program(t, to_spill)
        end
        else
          MOVE(s,src,dst) :: rewrite_program(t, to_spill)
      end
    | JUMP(s)::t -> (JUMP s) :: rewrite_program(t, to_spill)
    | LABEL(s)::t -> (LABEL s) :: rewrite_program(t, to_spill)
    | COMMENT(s)::t -> (COMMENT s) :: rewrite_program(t, to_spill)
    end
  in
  begin
    if (!E.debug) then print_instrlist !curprog;
    let to_spill = ref [-1] in
    let interflist: regalloc_t list list = get_live_in curprog !maxtemp in
    if (!E.debug) then print_instrlist !curprog;
    if (!E.debug) then print_live_in interflist;
    chaitin_init true mygraph (interflist, numregs, !maxtemp, curprog);
    while (!to_spill <> []) do
      let interflist = get_live_in curprog !maxtemp in
      chaitin_init false mygraph (interflist, numregs, !maxtemp, curprog);
      to_spill := chaitin_iter(mygraph, numregs);
      if (!E.debug) then print_igraph mygraph;
      List.iter (fun to_spill ->
        let newslot = get_new_stackslot() in
        ignore(E.debugf "Spilling temp %d to slot %d..." to_spill newslot);
        Hashtbl.replace (snd !mygraph) (TEMP to_spill)
                                       (true, (I_STACKSLOT newslot), []);
        curprog := rewrite_program(!curprog, TEMP to_spill);
        ignore(E.debugf " done.\n")
      ) !to_spill;
      if (!E.debug) then print_instrlist !curprog;
      ignore(E.debugf "\n--------------------------\n")
    done;
    (*
       This last part is just a bit of extra credit.  It offends
       my sense of elegance to leave in all those extraneous |MOVE|
       instructions, so let's get rid of them.
    *)
    let alloc_fn = chaitin_final mygraph in
    curprog := trim_extra_moves alloc_fn !curprog;
    (!curprog, !maxslot, alloc_fn)
  end