(*
 * Copyright (c) 2001 Stefan Kral
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *)

(* This module includes code for the translation of effective addresses. *)


open List
open Util
open P4Basics
open VSimdBasics
open P4RegisterAllocationBasics
open P4Unparsing
open VSimdUnparsing

open NonDetMonad

let isMulExpr one (rreg,rregfileentry) = match rregfileentry with 
  | ITmpHoldsProduct(base,factor) when base=one -> Some(factor,rreg)
  | IVarHoldsProduct(base,_,_) when base=one -> Some(1,rreg)
  | IVarHoldsProduct(_,base,factor) when base=one -> Some(factor,rreg)
  | IHolds reg when reg = one 	       -> Some(1,rreg)
  | _ -> None 

let isNTimesOne n one (rreg,rregfileentry) = match rregfileentry with 
  | IHolds reg when reg=one && n=1 	    -> true
  | ITmpHoldsProduct(base,k) when base=one && n=k -> true
  | IVarHoldsProduct(_,base,k) when base=one && n=k -> true
  | IVarHoldsProduct(base,_,_) when base=one && n=1 -> true
  | _ -> false


let find_leaM' one search_depth_limit n = 
  let getNewIntRegM () = 					(* LRU *)
    fetchStateM >>= fun riregfile0 ->
      selectM riregfile0 >>= fun ((reg,regfileentry),riregfile) ->
	posAssertM (riregfileentryIsFree regfileentry) >>
	  storeStateM riregfile >>
	    unitM reg in

  let availableConstantsM () =
    fetchStateM >>= fun riregfile ->
      memberM riregfile >>= fun reg_content ->
	optionToValueM (isMulExpr one reg_content) in

  let onestepM (x,x') z' = 			(* redundant solutions? *)
    disjM [(fun _ -> 
	      memberM [1;2;4;8] >>= fun s -> 
	        unitM (x*(s+1), P4R_IntLoadEA(P4R_RISID(x',x',s,0), z')));
	   (fun _ -> 
	      memberM [4;8] >>= fun s -> 
	        unitM (x*s, P4R_IntLoadEA(P4R_SID(x',s,0), z')));
	   (fun _ -> 
	      memberM [1;2;4;8] >>= fun s -> 
	        availableConstantsM () >>= fun (k,k') -> 
		  unitM (x*s+k, P4R_IntLoadEA(P4R_RISID(k',x',s,0), z')));
	   (fun _ -> 
	      memberM [2;4;8] >>= fun s -> 
		availableConstantsM () >>= fun (k,k') -> 
		  unitM (x+k*s, P4R_IntLoadEA(P4R_RISID(x',k',s,0), z')))] in


  let addNewMultipleOfOneM z reg =
    fetchStateM >>= fun riregfile0 ->
      negAssertM (optionIsSome (find_elem (isNTimesOne z one) riregfile0)) >>
	storeStateM (riregfile0 @ [(reg, ITmpHoldsProduct(one,z))]) in

  let rec nstepsM instrs (x,x') depth_limit = 
    if x = n then
      unitM (rev instrs, x')
    else 
      fetchStateM >>= fun riregfile ->
        match find_elem (isNTimesOne n one) riregfile with
	  | Some(rreg,_) -> unitM ([],rreg)
	  | None ->
	      posAssertM (depth_limit > 0) >>= fun _ ->
	        getNewIntRegM () >>= fun reg ->
	          onestepM (x,x') reg >>= fun (z,instr) ->
		    addNewMultipleOfOneM z reg >>
		      nstepsM (instr::instrs) (z,reg) (pred depth_limit) in


  availableConstantsM () >>= fun (k,one') ->
    posAssertM (k=1) >>= fun _ ->
      (   ( fun _ ->  (* first try: DFID with a sequence of LEA instructions *)
	      betweenM 0 search_depth_limit >>= nstepsM [] (1,one')
	  )
      ||| ( fun _ -> 	 (* alternative: use the expensive IMUL instruction *)
	      getNewIntRegM () >>= fun reg ->
		addNewMultipleOfOneM n reg >>
		  unitM ([P4R_IntCpyUnaryOp(P4_IMulImm n, one', reg)], reg)
	  )
      )


let find_leaM one search_depth_limit n = 
  find_leaM' one search_depth_limit n >>= fun (instrs',idx') ->
    fetchStateM >>= fun riregfile' ->
      unitM (instrs',idx',riregfile')


(* TRY TO SUBSTITUTE 'IMUL' WITH 'LEA' WHEREVER POSSIBLE *)
let loadea_factor mapInt riregfile = function
  | P4V_RID(base,ofs) ->
      ([], P4R_RID(mapInt base,ofs), riregfile)
  | P4V_SID(idx,scalar,offset) ->
      if mem scalar [1;2;4;8] then
        ([], P4R_SID(mapInt idx,scalar,offset), riregfile)
      else
	failwith "loadea_factor: got SID(i,_,_) with i not in [1;2;4;8]"
  | P4V_RISID(base,idx,scalar,offset) -> 
      let k = find (fun i -> scalar mod i = 0) [8;4;2;1] in
        (match runM (find_leaM idx !Magic.imul_to_lea_limit)
		    (scalar/k) 
		    riregfile with
	  | None -> failwith "loadea_factor: internal error"
	  | Some (instrs,idx',riregfile') -> 
	      (instrs, P4R_RISID(mapInt base,idx',k,offset), riregfile'))

