-- | Expand out synthetic instructions into single machine instrs.
module SPARC.CodeGen.Expand (
	expandTop
)

where

import SPARC.Instr
import SPARC.Imm
import SPARC.AddrMode
import SPARC.Regs
import SPARC.Ppr	()
import Instruction
import Reg
import Size
import Cmm


import Outputable
import OrdList

-- | Expand out synthetic instructions in this top level thing
expandTop :: NatCmmTop Instr -> NatCmmTop Instr
expandTop top@(CmmData{})
	= top

expandTop (CmmProc info lbl params (ListGraph blocks))
	= CmmProc info lbl params (ListGraph $ map expandBlock blocks)


-- | Expand out synthetic instructions in this block
expandBlock :: NatBasicBlock Instr -> NatBasicBlock Instr

expandBlock (BasicBlock label instrs)
 = let	instrs_ol	= expandBlockInstrs instrs
 	instrs'		= fromOL instrs_ol
   in	BasicBlock label instrs'


-- | Expand out some instructions
expandBlockInstrs :: [Instr] -> OrdList Instr
expandBlockInstrs []	= nilOL
	
expandBlockInstrs (ii:is)
 = let	ii_doubleRegs	= remapRegPair ii
	is_misaligned	= expandMisalignedDoubles ii_doubleRegs

   in	is_misaligned `appOL` expandBlockInstrs is
   


-- | In the SPARC instruction set the FP register pairs that are used
--	to hold 64 bit floats are refered to by just the first reg 
--	of the pair. Remap our internal reg pairs to the appropriate reg.
--
--	For example:
--	    ldd [%l1], (%f0 | %f1)
--
--	gets mapped to
--	    ldd [$l1], %f0
--
remapRegPair :: Instr -> Instr
remapRegPair instr
 = let	patchF reg
 	 = case reg of
	 	RegReal (RealRegSingle _) 	
			-> reg

		RegReal (RealRegPair r1 r2)	

			-- sanity checking
		 	| r1 	     >= 32
			, r1 	     <= 63
			, r1 `mod` 2 == 0	
			, r2         == r1 + 1	
			-> RegReal (RealRegSingle r1)

			| otherwise		
			-> pprPanic "SPARC.CodeGen.Expand: not remapping dodgy looking reg pair " (ppr reg)

		RegVirtual _
			-> pprPanic "SPARC.CodeGen.Expand: not remapping virtual reg " (ppr reg)
			
   in	patchRegsOfInstr instr patchF




-- Expand out 64 bit load/stores into individual instructions to handle
--	possible double alignment problems.
--
--	TODO: 	It'd be better to use a scratch reg instead of the add/sub thing.
--		We might be able to do this faster if we use the UA2007 instr set
--		instead of restricting ourselves to SPARC V9.
--
expandMisalignedDoubles :: Instr -> OrdList Instr
expandMisalignedDoubles instr

	-- Translate to:
	--    add g1,g2,g1
	--    ld  [g1],%fn
	--    ld  [g1+4],%f(n+1)
	--    sub g1,g2,g1           -- to restore g1
 	| LD FF64 (AddrRegReg r1 r2) fReg	<- instr
	= 	toOL	[ ADD False False r1 (RIReg r2) r1
			, LD  FF32  (AddrRegReg r1 g0) 		fReg
			, LD  FF32  (AddrRegImm r1 (ImmInt 4))	(fRegHi fReg)
			, SUB False False r1 (RIReg r2) r1 ]

	-- Translate to
	--    ld  [addr],%fn
	--    ld  [addr+4],%f(n+1)
	| LD FF64 addr fReg			<- instr
	= let	Just addr'	= addrOffset addr 4
	  in	toOL	[ LD  FF32  addr	fReg
	  		, LD  FF32  addr'	(fRegHi fReg) ]

	-- Translate to:
	--    add g1,g2,g1
	--    st  %fn,[g1]
	--    st  %f(n+1),[g1+4]
	--    sub g1,g2,g1           -- to restore g1
	| ST FF64 fReg (AddrRegReg r1 r2)	<- instr
	= 	toOL	[ ADD False False r1 (RIReg r2) r1
			, ST  FF32  fReg           (AddrRegReg r1 g0) 		
			, ST  FF32  (fRegHi fReg)  (AddrRegImm r1 (ImmInt 4))	
			, SUB False False r1 (RIReg r2) r1 ]

	-- Translate to
	--    ld  [addr],%fn
	--    ld  [addr+4],%f(n+1)
	| ST FF64 fReg addr			<- instr
	= let	Just addr'	= addrOffset addr 4
	  in	toOL	[ ST  FF32  fReg           addr	
	  		, ST  FF32  (fRegHi fReg)  addr'	 ]

	-- some other instr
	| otherwise
	= unitOL instr



-- | The the high partner for this float reg.	
fRegHi :: Reg -> Reg
fRegHi (RegReal (RealRegSingle r1))
	| r1		>= 32
	, r1		<= 63
	, r1 `mod` 2 == 0
	= (RegReal $ RealRegSingle (r1 + 1))
	
-- Can't take high partner for non-low reg.
fRegHi reg
	= pprPanic "SPARC.CodeGen.Expand: can't take fRegHi from " (ppr reg)