(***********************************************************************)
(*                                                                     *)
(*                           Objective Caml                            *)
(*                                                                     *)
(*            Xavier Leroy, projet Cristal, INRIA Rocquencourt         *)
(*                                                                     *)
(*  Copyright 1996 Institut National de Recherche en Informatique et   *)
(*  Automatique.  Distributed only by permission.                      *)
(*                                                                     *)
(***********************************************************************)

(* $Id: reload.ml,v 1.14 1996/09/18 14:16:16 xleroy Exp $ *)

(* Insert load/stores for pseudoregs that got assigned to stack locations.
   Insert moves to comply with calling conventions, etc. *)

open Misc
open Reg
open Mach

let redo_regalloc = ref false

let access_stack r =
  try
    for i = 0 to Array.length r - 1 do
      match r.(i).loc with Stack _ -> raise Exit | _ -> ()
    done;
    false
  with Exit ->
    true

let makereg r =
  match r.loc with
    Unknown -> fatal_error "Reload.makereg"
  | Reg _ -> r
  | Stack _ ->
      if Proc.num_available_registers.(Proc.register_class r) = 0
      then r
      else begin
        redo_regalloc := true;
        let newr = Reg.clone r in
        (* Strongly discourage spilling this register *)
        newr.spill_cost <- 100000;
        newr
      end

let makeregs rv =
  let n = Array.length rv in
  let newv = Array.create n Reg.dummy in
  for i = 0 to n-1 do newv.(i) <- makereg rv.(i) done;
  newv

let makereg1 rv =
  let newv = Array.copy rv in
  newv.(0) <- makereg rv.(0);
  newv

let insert_move src dst next =
  if src.loc = dst.loc
  then next
  else instr_cons (Iop Imove) [|src|] [|dst|] next

let insert_moves src dst next =
  let rec insmoves i =
    if i >= Array.length src
    then next
    else insert_move src.(i) dst.(i) (insmoves (i+1))
  in insmoves 0

let reload_round = ref 0


let rec reload i =
  match i.desc with
    (* For function calls, returns, etc: the arguments and results are
       already at the correct position (e.g. on stack for some arguments).
       However, something needs to be done for the function pointer in
       indirect calls. *)
    Iend | Ireturn | Iop(Itailcall_imm _) | Iraise -> i
  | Iop(Itailcall_ind) ->
      let newarg = makereg1 i.arg in
      insert_moves i.arg newarg
        (instr_cons_live i.desc newarg i.res i.live i.next)
  | Iop(Icall_imm _ | Iextcall(_, _)) ->
      instr_cons_live i.desc i.arg i.res i.live (reload i.next)
  | Iop(Icall_ind) ->
      let newarg = makereg1 i.arg in
      insert_moves i.arg newarg
        (instr_cons_live i.desc newarg i.res i.live (reload i.next))
  | Iop op ->
      (* Let the machine description tell us whether some arguments / results
         can reside on the stack *)
      let (newarg, newres) =
        try
          Proc.reload_operation makereg !reload_round op i.arg i.res
        with Proc.Use_default ->
          (* By default, assume that arguments and results must reside
             in hardware registers. For moves, allow one arg or one
             res to be stack-allocated, but do something for
             stack-to-stack moves *)
          match op with
            Imove | Ireload | Ispill ->
              begin match i.arg.(0), i.res.(0) with
                {loc = Stack s1}, {loc = Stack s2} when s1 <> s2 ->
                  ([| makereg i.arg.(0) |], i.res)
              | _ ->
                  (i.arg, i.res)
              end
          | _ ->
              (makeregs i.arg, makeregs i.res) in
      insert_moves i.arg newarg
        (instr_cons_live i.desc newarg newres i.live
          (insert_moves newres i.res
            (reload i.next)))
  | Iifthenelse(tst, ifso, ifnot) ->
      (* Let the machine description tell us whether some arguments / results
         can reside on the stack *)
      let newarg =
        try
          Proc.reload_test makereg !reload_round tst i.arg
        with Proc.Use_default ->
          makeregs i.arg in
      insert_moves i.arg newarg      
        (instr_cons (Iifthenelse(tst, reload ifso, reload ifnot)) newarg [||]
          (reload i.next))
  | Iswitch(index, cases) ->
      let newarg = makeregs i.arg in
      insert_moves i.arg newarg      
        (instr_cons (Iswitch(index, Array.map reload cases)) newarg [||]
          (reload i.next))
  | Iloop body ->
      instr_cons (Iloop(reload body)) [||] [||] (reload i.next)
  | Icatch(body, handler) ->
      instr_cons (Icatch(reload body, reload handler)) [||] [||]
        (reload i.next)
  | Iexit ->
      instr_cons Iexit [||] [||] dummy_instr
  | Itrywith(body, handler) ->
      instr_cons (Itrywith(reload body, reload handler)) [||] [||]
        (reload i.next)

let fundecl round f =
  redo_regalloc := false;
  reload_round := round;
  let new_body = reload f.fun_body in
  ({fun_name = f.fun_name; fun_args = f.fun_args;
    fun_body = new_body; fun_fast = f.fun_fast},
   !redo_regalloc)

