(*
 * 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 the instruction scheduler for basic blocks. *)

open Id
open List
open Util
open P4Basics
open P4InstructionSchedulingBasics
open P4ExecutionModel

(****************************************************************************)

type dependency =			(* INSTRUCTION DEPENDENCY ***********)
  | RAW of Id.t * Id.t * int		(*   Read after Write (w/Latency)   *)
  | WAR of Id.t * Id.t			(*   Write after Read 		    *)
  | WAW of Id.t * Id.t 			(*   Write after Write		    *)

let depToSucc    = function RAW(r,_,_) -> r | WAR(w,_) -> w | WAW(w,_) -> w
let depToLatency = function RAW(_,_,l) -> l | WAR _    -> 0 | WAW _    -> 0

(****************************************************************************)

let calc_forward_dependencies instrs = 
  let calc_fw_deps (idmap,deps,lastreads,lastwrite) instr =
    let id = Id.makeNew () in

    let addDepsR r_res = match resmap_find r_res lastwrite with	     (* RAW *)
      | Some (w_id,w_latency) -> idmap_addE w_id (RAW(id,w_id,w_latency))
      | None -> return
    and addDepsW (w_res,_) deps = 			     (* WAW and WAR *)
      fold_right (fun r_id -> idmap_addE r_id (WAR(id,r_id)))
		 (resmap_findE w_res lastreads) 
		 (match resmap_find w_res lastwrite with
      		    | Some (w_id,_) -> idmap_addE w_id (WAW(id,w_id)) deps
      		    | None -> deps) in

    let (rs,ws)    = (p4rinstrToSrcp4rops instr, p4rinstrToDstp4rops instr) in
    let lastreads' = fold_right (resmap_addE' id) rs lastreads in
      (IdMap.add id instr idmap,
       fold_right addDepsW ws (fold_right addDepsR rs (IdMap.add id [] deps)),
       fold_right (fun (res,_) -> ResMap.add res []) ws lastreads',
       fold_right (fun (res,lat) -> ResMap.add res (id,lat)) ws lastwrite) in

  let s0 = (IdMap.empty, IdMap.empty, ResMap.empty, ResMap.empty) in
  let (idmap,fw_deps,_,_) = fold_left calc_fw_deps s0 instrs in
    ((fun id -> IdMap.find id idmap), fw_deps)

(****************************************************************************)

let calc_backward_dependencies_count fw_deps =
  IdMap.fold (fun _ -> fold_right (fun dep -> idmap_inc (depToSucc dep)))
	      fw_deps 
	      (IdMap.map toZero fw_deps)

(****************************************************************************)

let calc_critical_path_lengths fw_deps bw_deps_cnt = 
  let fold_start_instrs id cnt = if cnt = 0 then cons id else return in
  let startinstrs = IdMap.fold fold_start_instrs bw_deps_cnt [] in

  let rec calc_cplen' id cplen = 
    if idmap_exists id cplen then cplen else calc_cplen'' id cplen

  and calc_cplen'' id cplen = match IdMap.find id fw_deps with
    | [] -> IdMap.add id 0 cplen
    | deps ->
        let cplen' = fold_right calc_cplen' (map depToSucc deps) cplen in
	let depToCPLen d = depToLatency d + IdMap.find (depToSucc d) cplen' in
          IdMap.add id (max_list (map depToCPLen deps)) cplen' in

  let cplens = fold_right calc_cplen' startinstrs IdMap.empty in
    fun id -> IdMap.find id cplens

(*****************************************************************************)

(* 'waiting' instrs are blocked until some other instrs have been issued. *)
type bwdepscnt_est = BWCnt_EST of int * int

(* a 'preready' instruction i needs to wait until t >= est(i) *)
type est_id = EST_Id of int * Id.t
module ESTIdSet = Set.Make(struct type t = est_id let compare = compare end)

(* 'ready' instructions may be issued at any time *)
type cp_id = CP_Id of int * Id.t


(* used when sorting a list of ready instructions (in descending order) *)
let cmp_cpid (CP_Id(x_cplen,_)) (CP_Id(y_cplen,_)) = compare y_cplen x_cplen

(****************************************************************************)

let flatinssched idmap fw_deps cplens =
  (* move instructions from 'preready' to 'ready' *)
  let rec add_new_ready t = function
    | (_, s) as pair when ESTIdSet.is_empty s -> pair
    | (ready,preready) as pair ->
        match ESTIdSet.min_elt preready with
	  | EST_Id(est,_) when t < est -> pair
	  | EST_Id(_,id) as min_el ->
	      let ready' = insert_list cmp_cpid (CP_Id(cplens id,id)) ready in
	        add_new_ready t (ready', ESTIdSet.remove min_el preready)

  (* adapt earliest starting time. decrement the number of bw deps of an
   * instruction. if it is zero, move the instr from waiting to preready. *)
  and adapt_est' t dep (preready,waiting) =
    let id = depToSucc dep in
    let (r_bwdepslen,r_est) = IdMap.find id waiting in
    let r_est' = max (t + (depToLatency dep)) r_est in
      if r_bwdepslen > 1 then	     (* # of other bw_deps of the successor *)
	(preready, IdMap.add id (pred r_bwdepslen,r_est') waiting)
      else
	(ESTIdSet.add (EST_Id(r_est',id)) preready, IdMap.remove id waiting)

  (* enforce forward dependencies for one instruction (starting at t) *)
  and adapt_est t id = fold_right (adapt_est' t) (IdMap.find id fw_deps)

  (* the actual instruction scheduling loop *)
  and flatinssched' t nr_freeslots instrs_in_t instrs
		    ((ready, preready) as ready_preready) waiting =

    let complete () = rev (instrs_in_t @ instrs)
    and start_new_cycle t' = 
      flatinssched' t' p4SlotsPerCycle [] (instrs_in_t @ instrs)
		    (add_new_ready t' ready_preready) waiting in

    match (ready,preready,waiting) with
      | ([],p,w) when ESTIdSet.is_empty p && w = IdMap.empty -> complete ()
      | ([],p,_) when not (ESTIdSet.is_empty p) -> start_new_cycle (succ t)
      | ([],_,_) -> failwith "flatinssched: inconsistent state!"
      | (_::_,_,_) when nr_freeslots = 0 -> start_new_cycle (succ t)
      | (_::_ as ready,_,_) ->
	  (* select an instr with the longest critical path that may be issued
	   * simultaneously with the ones already selected earlier. *)
	  let instrs_in_t' = map get3of3 instrs_in_t in
	  let instrMayIssueInThisCycle (CP_Id(_,id)) = 
	    p4rinstrsCanIssueInOneCycle (idmap id::instrs_in_t') in
	  match selectFirst instrMayIssueInThisCycle ready with
            | None -> start_new_cycle (succ t)
            | Some((CP_Id(cplen,id),ready')) ->
	        let preready',waiting' = adapt_est t id (preready,waiting) in
		let instrs_in_t' = (t,cplen,idmap id)::instrs_in_t in
	          flatinssched' t (pred nr_freeslots) instrs_in_t' instrs
				(add_new_ready t (ready',preready')) waiting'

 in flatinssched' 0 p4SlotsPerCycle [] []


(* EXPORTED FUNCTIONS *******************************************************)

let p4rinstrsToInstructionscheduled instrs = 
  let (idmap,fw_deps) = calc_forward_dependencies instrs in
  let bw_deps_cnt = calc_backward_dependencies_count fw_deps in
  let toCP = calc_critical_path_lengths fw_deps bw_deps_cnt in
  let foldReady id (n,_) = if n=0 then cons (CP_Id(toCP id,id)) else return in
  let all = IdMap.map (fun cnt -> (cnt, 0)) bw_deps_cnt in
  let ready = sort cmp_cpid (IdMap.fold foldReady all []) in
  let waiting = fold_right (fun (CP_Id(_,i)) -> IdMap.remove i) ready all in
    flatinssched idmap fw_deps toCP (ready, ESTIdSet.empty) waiting

