(*
 * Copyright (c) 2000-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 functions for unparsing p4vinstrs and p4rinstrs. *)

open List
open Util
open Variable
open Number
open VSimdBasics
open VSimdUnparsing
open P4Basics
open Printf


(* CODE FOR UNPARSING (P4) *************************************************)

let mmx_reg_size = ref P4_QWord

let stackpointer_adjustment = ref 0

let p4memopToString = function
  | P4_MVar var_name -> 
      var_name
  | P4_MConst const_name -> 
      const_name
  | P4_MStackCell(P4_INTStack,_) -> 
      failwith "p4memopToString: unsupported!"
  | P4_MFunArg i -> 
      sprintf "%d(%%esp)" (i*4 + !stackpointer_adjustment)
  | P4_MStackCell(P4_MMXStack,p) -> 
      sprintf "fftw_stackcell_%d" p

let p4operandsizeToMovstring = function 
  | P4_QWord -> "movsd"
  | P4_OWord -> "movapd"

let p4intbinopToString = function 
  | P4_IAdd -> "addl"  
  | P4_ISub -> "subl"

let p4intunaryopToString = function
  | P4_IPush	    -> "pushl"
  | P4_IPop	    -> "popl"
  | P4_INegate	    -> "negl"
  | P4_IInc    	    -> "incl"
  | P4_IDec    	    -> "decl"
  | P4_IClear  	    -> "clrl"
  | P4_IAddImm _    -> "addl"
  | P4_ISubImm _    -> "subl"
  | P4_IShlImm _    -> "sall"
  | P4_IShrImm _    -> "sral"
  | P4_ILoadValue _ -> "movl"

let p4intcpyunaryopToString = function
  | P4_ICopy     -> "movl"
  | P4_IMulImm _ -> "imull"

let p4simdcpyunaryopToString = function 
  | P4_FPId -> "movapd"

let p4simdcpyunaryopmemToString = function 
  | P4_FPId -> "movapd" (* p4operandsizeToMovstring !mmx_reg_size *)

let p4simdunaryopToString = function
  | P4_FPChs V_Lo  -> "mulsd" 
  | P4_FPChs _	   -> "mulpd" 
  | P4_FPMulC1 _   -> "mulsd"
  | P4_FPMulC2 _   -> "mulpd"

let p4simdbinopToString = function
  | P4_FPAdd1    -> "addsd"
  | P4_FPAdd2    -> "addpd"
  | P4_FPSub1    -> "subsd"
  | P4_FPSub2    -> "subpd"
  | P4_FPMul1    -> "mulsd"
  | P4_FPMul2    -> "mulpd"
  | P4_UnpckLo   -> "unpcklpd"
  | P4_UnpckHi   -> "unpckhpd"
  | P4_Shuffle _ -> "shufpd"

let p4branchconditionToString = function
  | P4_BCond_NotZero     -> "nz"
  | P4_BCond_Zero        -> "z"
  | P4_BCond_GreaterZero -> "g"
  | P4_BCond_EqualZero   -> "e"


(* CODE SHARED FOR UNPARSING P4V AND P4R INSTRUCTIONS ***********************)

let p4mmxstackcellToString c = p4memopToString (P4_MStackCell(P4_MMXStack,c))

let p4simdunaryopToArgstring = function
  | P4_FPChs p	    -> vsimdposToChsconstnamestring p
  | P4_FPMulC1 n    -> Number.unparse n
  | P4_FPMulC2(n,m) -> Number.unparse n ^ Number.unparse m

let p4intunaryopToArgstrings = function
  | P4_IAddImm c    -> ["$" ^ intToString c]
  | P4_ISubImm c    -> ["$" ^ intToString c]
  | P4_IShlImm c    -> ["$" ^ intToString c]
  | P4_IShrImm c    -> ["$" ^ intToString c]
  | P4_ILoadValue c -> ["$" ^ intToString c]
  | P4_INegate	    -> []
  | P4_IInc	    -> []
  | P4_IDec	    -> []
  | P4_IClear	    -> []
  | P4_IPush	    -> []
  | P4_IPop	    -> []

let p4intcpyunaryopToArgstrings = function
  | P4_ICopy	 -> []
  | P4_IMulImm i -> ["$" ^ intToString i]

(* needed for p4vaddrToString/p4raddrToString *)
let offsetToString x = if x = 0 then "" else intToString x
let scalarToString x = if x <> 1 then "," ^ intToString x else ""


(* CODE FOR UNPARSING (P4V) ************************************************)

let p4vaddrToString vaddr = match p4vaddrToSimplified vaddr with
  | P4V_RID(base,ofs) -> 
      sprintf "%s(%s)" 
	      (offsetToString ofs) 
	      (vintregToString base)
  | P4V_SID(idx,scalar,ofs) -> 
      sprintf "%s(,%s%s)" 
	      (offsetToString ofs) 
	      (vintregToString idx) 
	      (scalarToString scalar)
  | P4V_RISID(base,idx,scalar,ofs) -> 
      sprintf "%s(%s,%s%s)" 
	      (offsetToString ofs) 
	      (vintregToString base) 
	      (vintregToString idx) 
	      (scalarToString scalar)

let p4vbranchtargetToString (P4V_BTarget_Named name) = name

let p4simdbinopToArgstrings = function
  | P4_Shuffle i -> ["$" ^ (intToString i)]
  | _ -> [] 

let p4vinstrToArgstrings = function
  | P4V_IntLoadEA(s,d) 		 -> [p4vaddrToString s; vintregToString d]
  | P4V_IntCpyUnaryOp(op,s,d) 	 -> p4intcpyunaryopToArgstrings op @
				    [vintregToString s; vintregToString d]
  | P4V_IntUnaryOp(op,sd) 	 -> p4intunaryopToArgstrings op @
				    [vintregToString sd]
  | P4V_IntUnaryOpMem(op,sd)	 -> p4intunaryopToArgstrings op @
				    [p4memopToString sd]
  | P4V_IntBinOp(_,s,sd)	 -> [vintregToString s; vintregToString sd]
  | P4V_IntBinOpMem(_,s,sd)	 -> [p4memopToString s; vintregToString sd]
  | P4V_IntLoadMem(s,d)		 -> [p4memopToString s; vintregToString d]
  | P4V_IntStoreMem(s,d)	 -> [vintregToString s; p4memopToString d]
  | P4V_SimdLoad(_,s,d)		 -> [p4vaddrToString s; vsimdregToString d]
  | P4V_SimdStore(s,_,d)	 -> [vsimdregToString s; p4vaddrToString d]
  | P4V_SimdLoad1(s,_,d)	 -> [p4vaddrToString s; vsimdregToString d]
  | P4V_SimdStore1(_,s,d)	 -> [vsimdregToString s; p4vaddrToString d]
  | P4V_SimdUnaryOp(op,sd)	 -> [p4simdunaryopToArgstring op;
				     vsimdregToString sd]
  | P4V_SimdCpyUnaryOp(_,s,d)	 -> [vsimdregToString s; vsimdregToString d]
  | P4V_SimdCpyUnaryOpMem(_,s,d) -> [p4memopToString s; vsimdregToString d]
  | P4V_SimdBinOp(op,s,sd)  	 -> p4simdbinopToArgstrings op @
				      [vsimdregToString s; vsimdregToString sd]
  | P4V_SimdBinOpMem(op,s,sd)	 -> p4simdbinopToArgstrings op @ 
				      [p4memopToString s; vsimdregToString sd]
  | P4V_RefInts xs		 -> map vintregToString xs
  | P4V_Label _			 -> []
  | P4V_SimdLoadStoreBarrier     -> []
  | P4V_Jump d			 -> [p4vbranchtargetToString d]
  | P4V_CondBranch(_,d)	 	 -> [p4vbranchtargetToString d]
  | P4V_SimdPromiseCellSize size -> [p4operandsizeToString size]

let p4vinstrToInstrstring = function
  | P4V_IntLoadEA _		  -> "leal"
  | P4V_IntUnaryOp(op,_) 	  -> p4intunaryopToString op
  | P4V_IntUnaryOpMem(op,_) 	  -> p4intunaryopToString op
  | P4V_IntCpyUnaryOp(op,_,_) 	  -> p4intcpyunaryopToString op
  | P4V_IntBinOp(op,_,_) 	  -> p4intbinopToString op
  | P4V_IntBinOpMem(op,_,_) 	  -> p4intbinopToString op
  | P4V_IntLoadMem _		  -> "movl"
  | P4V_IntStoreMem _		  -> "movl"
  | P4V_SimdLoad(size,_,_)	  -> p4operandsizeToMovstring size
  | P4V_SimdStore(_,size,_)	  -> p4operandsizeToMovstring size
  | P4V_SimdLoad1(_,V_Lo,_)	  -> "movlpd"
  | P4V_SimdLoad1(_,V_Hi,_)	  -> "movhpd"
  | P4V_SimdLoad1(_,V_LoHi,_)	  -> failwith "p4vinstrToInstrstring"
  | P4V_SimdStore1(V_Lo,_,_)	  -> "movlpd"
  | P4V_SimdStore1(V_Hi,_,_)	  -> "movhpd"
  | P4V_SimdStore1(V_LoHi,_,_)	  -> failwith "p4vinstrToInstrstring"
  | P4V_SimdUnaryOp(op,_) 	  -> p4simdunaryopToString op
  | P4V_SimdCpyUnaryOp(op,_,_)    -> p4simdcpyunaryopToString op
  | P4V_SimdCpyUnaryOpMem(op,_,_) -> p4simdcpyunaryopmemToString op
  | P4V_SimdBinOp(op,_,_) 	  -> p4simdbinopToString op
  | P4V_SimdBinOpMem(op,_,_) 	  -> p4simdbinopToString op
  | P4V_RefInts _		  -> "refints"
  | P4V_Label lbl		  -> lbl ^ ":"
  | P4V_Jump _			  -> "jmp"
  | P4V_CondBranch(cond,_)	  -> "j" ^ p4branchconditionToString cond
  | P4V_SimdLoadStoreBarrier 	  -> "loadstorebarrier"
  | P4V_SimdPromiseCellSize _     -> "promise_mmx_stackcell_size"

let p4vinstrToString i =
  (p4vinstrToInstrstring i) ^ 
	" " ^ (stringlistToString ", " (p4vinstrToArgstrings i))


(* CODE FOR UNPARSING (P4R) *************************************************)

let p4rintregToRawString (P4R_IntReg name) = name
let p4rmmxregToRawString (P4R_MMXReg name) = name

let p4rintregToString (P4R_IntReg name) = "%" ^ name
let p4rmmxregToString (P4R_MMXReg name) = "%" ^ name

(* warning: addresses with base ''%esp'' are *not* adjusted here. *)
let p4raddrToString raddr = match p4raddrToSimplified raddr with
  | P4R_RID(base,ofs) -> 
      sprintf "%s(%s)" 
	      (offsetToString ofs) 
	      (p4rintregToString base)
  | P4R_SID(idx,scalar,ofs) -> 
      sprintf "%s(,%s,%d)" 
	      (offsetToString ofs) 
	      (p4rintregToString idx) 
	      scalar
  | P4R_RISID(base,index,scalar,ofs) -> 
      sprintf "%s(%s,%s%s)" 
	      (offsetToString ofs)
	      (p4rintregToString base) 
	      (p4rintregToString index)
	      (scalarToString scalar)

let p4rbranchtargetToString (P4R_BTarget_Named name) = name

let p4rinstrToArgstrings = function
  | P4R_SimdLoadStoreBarrier	 -> []
  | P4R_SimdPromiseCellSize _    -> []
  | P4R_IntLoadMem(s,d) 	 -> [p4memopToString s; p4rintregToString d]
  | P4R_IntStoreMem(s,d)	 -> [p4rintregToString s; p4memopToString d]

  | P4R_IntLoadEA(s,d) 		 -> [p4raddrToString s; p4rintregToString d]
  | P4R_IntUnaryOp(op,sd)	 -> p4intunaryopToArgstrings op @
				    [p4rintregToString sd]
  | P4R_IntUnaryOpMem(op,sd)	 -> p4intunaryopToArgstrings op @
				    [p4memopToString sd]
  | P4R_IntCpyUnaryOp(op,s,d)    -> p4intcpyunaryopToArgstrings op @
				    [p4rintregToString s; p4rintregToString d]
  | P4R_IntBinOp(_,s,sd) 	 -> [p4rintregToString s; p4rintregToString sd]
  | P4R_IntBinOpMem(_,s,sd) 	 -> [p4memopToString s; p4rintregToString sd]
  | P4R_SimdLoad(_,s,d) 	 -> [p4raddrToString s; p4rmmxregToString d]
  | P4R_SimdStore(s,_,d) 	 -> [p4rmmxregToString s; p4raddrToString d]
  | P4R_SimdLoad1(s,_,d) 	 -> [p4raddrToString s; p4rmmxregToString d]
  | P4R_SimdStore1(_,s,d) 	 -> [p4rmmxregToString s; p4raddrToString d]
  | P4R_SimdSpill(s,d) 		 -> [p4rmmxregToString s; 
				     p4mmxstackcellToString d]
  | P4R_SimdUnaryOp(op,sd) 	 -> [p4simdunaryopToArgstring op; 
				     p4rmmxregToString sd]
  | P4R_SimdCpyUnaryOp(_,s,d)    -> [p4rmmxregToString s; p4rmmxregToString d]
  | P4R_SimdCpyUnaryOpMem(_,s,d) -> [p4memopToString s; p4rmmxregToString d]
  | P4R_SimdBinOp(op,s,sd) 	 -> p4simdbinopToArgstrings op @
				      [p4rmmxregToString s; p4rmmxregToString sd]
  | P4R_SimdBinOpMem(op,s,sd) 	 -> p4simdbinopToArgstrings op @
				      [p4memopToString s; p4rmmxregToString sd]
  | P4R_Jump d			 -> [p4rbranchtargetToString d]
  | P4R_CondBranch(cond,d)	 -> [p4rbranchtargetToString d]
  | P4R_Ret			 -> []
  | P4R_Label _ 		 -> failwith "p4rinstrToArgstrings: label!"

let p4rinstrToInstrstring = function
  | P4R_SimdLoadStoreBarrier	  -> "/* simd data load/store barrier */"
  | P4R_SimdPromiseCellSize i     -> Printf.sprintf 
					"/* promise simd cell size = %d */"
					(p4operandsizeToInteger i)
  | P4R_IntLoadMem _		  -> "movl"
  | P4R_IntStoreMem _ 		  -> "movl"
  | P4R_IntLoadEA _		  -> "leal"
  | P4R_IntUnaryOp(op,_) 	  -> p4intunaryopToString op
  | P4R_IntUnaryOpMem(op,_) 	  -> p4intunaryopToString op
  | P4R_IntCpyUnaryOp(op,_,_)     -> p4intcpyunaryopToString op
  | P4R_IntBinOp(op,_,_)	  -> p4intbinopToString op
  | P4R_IntBinOpMem(op,_,_)	  -> p4intbinopToString op
  | P4R_SimdLoad(size,_,_)	  -> p4operandsizeToMovstring size
  | P4R_SimdStore(_,size,_)	  -> p4operandsizeToMovstring size
  | P4R_SimdLoad1(_,V_Lo,_)	  -> "movlpd"
  | P4R_SimdLoad1(_,V_Hi,_)	  -> "movhpd"
  | P4R_SimdLoad1(_,V_LoHi,_)	  -> failwith "p4rinstrToInstrstring"
  | P4R_SimdStore1(V_Lo,_,_)	  -> "movlpd"
  | P4R_SimdStore1(V_Hi,_,_)	  -> "movhpd"
  | P4R_SimdStore1(V_LoHi,_,_)    -> failwith "p4rinstrToInstrstring"
  | P4R_SimdSpill _ 		  -> p4operandsizeToMovstring !mmx_reg_size
  | P4R_SimdUnaryOp(op,_) 	  -> p4simdunaryopToString op
  | P4R_SimdCpyUnaryOp(op,_,_)    -> p4simdcpyunaryopToString op
  | P4R_SimdCpyUnaryOpMem(op,_,_) -> p4simdcpyunaryopmemToString op
  | P4R_SimdBinOp(op,_,_)	  -> p4simdbinopToString op
  | P4R_SimdBinOpMem(op,s,_)	  -> p4simdbinopToString op
  | P4R_Jump _			  -> "jmp"
  | P4R_CondBranch(cond,_)	  -> "j" ^ p4branchconditionToString cond
  | P4R_Ret			  -> "ret"
  | P4R_Label _			  -> failwith "p4rinstrToInstrstring: label!"

let p4rinstrToString = function
  | P4R_Label lbl -> sprintf "\t.p2align 4,,7\n%s:" lbl
  | i ->
      "\t" ^ p4rinstrToInstrstring i ^ 
	" " ^ (stringlistToString ", " (p4rinstrToArgstrings i))

(* Warning: this function produces side-effects. *)
let p4rinstrInitStackPointerAdjustment value = 
  stackpointer_adjustment := value

(* Warning: this function produces side-effects. *)
(* This function detects some instructions that usually would keep us from
 * omitting the frame-pointer. Since we *always* omit the frame-pointer,
 * this function raises an exception in these situations. *)
let p4rinstrAdaptStackPointerAdjustment = function
  | P4R_IntUnaryOp(P4_IPush,_) ->
      stackpointer_adjustment := !stackpointer_adjustment + 4
  | P4R_IntUnaryOp(P4_IPop,_)  ->
      stackpointer_adjustment := !stackpointer_adjustment - 4
  | P4R_IntUnaryOp(P4_IAddImm i,sd) when sd = p4rintreg_stackpointer ->
      stackpointer_adjustment := !stackpointer_adjustment - i
  | P4R_IntUnaryOp(P4_ISubImm i,sd) when sd = p4rintreg_stackpointer ->
      stackpointer_adjustment := !stackpointer_adjustment + i
  | P4R_SimdPromiseCellSize s ->
      mmx_reg_size := s
  | instr ->
      if p4rinstrUsesP4rintreg p4rintreg_stackpointer instr then
	failwith "p4rinstrAdaptStackPointerAdjustment: attempt to access esp!"
      else
	()
