module Kempe.Asm.X86.ControlFlow ( mkControlFlow
                                 , ControlAnn (..)
                                 ) where

-- seems to pretty clearly be faster
import           Control.Monad.State.Strict (State, evalState, gets, modify)
import           Data.Bifunctor             (first, second)
import           Data.Functor               (($>))
import qualified Data.IntSet                as IS
import qualified Data.Map                   as M
import           Data.Semigroup             ((<>))
import           Kempe.Asm.Type
import           Kempe.Asm.X86.Type

-- map of labels by node
type FreshM = State (Int, M.Map Label Int) -- TODO: map int to asm

runFreshM :: FreshM a -> a
runFreshM = flip evalState (0, mempty)

mkControlFlow :: [X86 AbsReg ()] -> [X86 AbsReg ControlAnn]
mkControlFlow instrs = runFreshM (broadcasts instrs *> addControlFlow instrs)

getFresh :: FreshM Int
getFresh = gets fst <* modify (first (+1))

lookupLabel :: Label -> FreshM Int
lookupLabel l = gets (M.findWithDefault (error "Internal error in control-flow graph: node label not in map.") l . snd)

broadcast :: Int -> Label -> FreshM ()
broadcast i l = modify (second (M.insert l i))

singleton :: AbsReg -> IS.IntSet
singleton = maybe IS.empty IS.singleton . toInt

  | Make sure 8 - bit and 64 - bit registers have no overlap .
--
  Also ca n't be called on abstract registers i.e. ' DataPointer ' or ' CArg1 ' .
-- This is kinda sus but it allows us to use an 'IntSet' for liveness analysis.
toInt :: AbsReg -> Maybe Int
toInt (AllocReg64 i) = Just i
toInt (AllocReg8 i)  = Just i
toInt _              = Nothing

fromList :: [AbsReg] -> IS.IntSet
fromList = foldMap singleton

addrRegs :: Addr AbsReg -> IS.IntSet
addrRegs (Reg r)              = singleton r
addrRegs (AddrRRPlus r r')    = fromList [r, r']
addrRegs (AddrRCPlus r _)     = singleton r
addrRegs (AddrRCMinus r _)    = singleton r
addrRegs (AddrRRScale r r' _) = fromList [r, r']

-- | Annotate instructions with a unique node name and a list of all possible
-- destinations.
addControlFlow :: [X86 AbsReg ()] -> FreshM [X86 AbsReg ControlAnn]
addControlFlow [] = pure []
addControlFlow ((Label _ l):asms) = do
    { i <- lookupLabel l
    ; (f, asms') <- next asms
    ; pure (Label (ControlAnn i (f []) IS.empty IS.empty) l : asms')
    }
addControlFlow ((Je _ l):asms) = do
    { i <- getFresh
    ; (f, asms') <- next asms
    ; l_i <- lookupLabel l -- TODO: is this what's wanted?
    ; pure (Je (ControlAnn i (f [l_i]) IS.empty IS.empty) l : asms')
    }
addControlFlow ((Jl _ l):asms) = do
    { i <- getFresh
    ; (f, asms') <- next asms
    ; l_i <- lookupLabel l
    ; pure (Jl (ControlAnn i (f [l_i]) IS.empty IS.empty) l : asms')
    }
addControlFlow ((Jle _ l):asms) = do
    { i <- getFresh
    ; (f, asms') <- next asms
    ; l_i <- lookupLabel l
    ; pure (Jle (ControlAnn i (f [l_i]) IS.empty IS.empty) l : asms')
    }
addControlFlow ((Jne _ l):asms) = do
    { i <- getFresh
    ; (f, asms') <- next asms
    ; l_i <- lookupLabel l
    ; pure (Jne (ControlAnn i (f [l_i]) IS.empty IS.empty) l : asms')
    }
addControlFlow ((Jge _ l):asms) = do
    { i <- getFresh
    ; (f, asms') <- next asms
    ; l_i <- lookupLabel l
    ; pure (Jge (ControlAnn i (f [l_i]) IS.empty IS.empty) l : asms')
    }
addControlFlow ((Jg _ l):asms) = do
    { i <- getFresh
    ; (f, asms') <- next asms
    ; l_i <- lookupLabel l
    ; pure (Jg (ControlAnn i (f [l_i]) IS.empty IS.empty) l : asms')
    }
addControlFlow ((Jump _ l):asms) = do
    { i <- getFresh
    ; nextAsms <- addControlFlow asms
    ; l_i <- lookupLabel l
    ; pure (Jump (ControlAnn i [l_i] IS.empty IS.empty) l : nextAsms)
    }
addControlFlow ((Call _ l):asms) = do
    { i <- getFresh
    ; nextAsms <- addControlFlow asms
    ; l_i <- lookupLabel l
    ; pure (Call (ControlAnn i [l_i] IS.empty IS.empty) l : nextAsms)
    }
addControlFlow (Ret{}:asms) = do
    { i <- getFresh
    ; nextAsms <- addControlFlow asms
    ; pure (Ret (ControlAnn i [] IS.empty IS.empty) : nextAsms)
    }
addControlFlow (asm:asms) = do
    { i <- getFresh
    ; (f, asms') <- next asms
    ; pure ((asm $> ControlAnn i (f []) (uses asm) (defs asm)) : asms')
    }

uses :: X86 AbsReg ann -> IS.IntSet
uses (PushReg _ r)       = singleton r
uses (PushMem _ a)       = addrRegs a
uses (PopMem _ a)        = addrRegs a
uses (MovRA _ _ a)       = addrRegs a
uses (MovAR _ a r)       = singleton r <> addrRegs a
uses (MovRR _ _ r)       = singleton r
uses (MovRRLower _ _ r)  = singleton r
uses (AddRR _ r r')      = fromList [r, r']
uses (SubRR _ r r')      = fromList [r, r']
uses (ImulRR _ r r')     = fromList [r, r']
uses (AddRC _ r _)       = singleton r
uses (SubRC _ r _)       = singleton r
uses (AddAC _ a _)       = addrRegs a
uses (MovABool _ a _)    = addrRegs a
uses (MovAC  _ a _)      = addrRegs a
uses (MovACi8 _ a _)     = addrRegs a
uses (XorRR _ r r')      = fromList [r, r']
uses (CmpAddrReg _ a r)  = singleton r <> addrRegs a
uses (CmpRegReg _ r r')  = fromList [r, r']
uses (CmpRegBool _ r _)  = singleton r
uses (CmpAddrBool _ a _) = addrRegs a
uses (LShiftLRR _ r r')  = fromList [r, r']
uses (LShiftRRR _ r r')  = fromList [r, r']
uses (AShiftRRR _ r r')  = fromList [r, r']
uses (MovRCi8 _ r _)     = singleton r
uses (MovACTag _ a _)    = addrRegs a
uses (IdivR _ r)         = singleton r
uses (DivR _ r)          = singleton r
uses Cqo{}               = IS.empty -- TODO?
uses (AndRR _ r r')      = fromList [r, r']
uses (OrRR _ r r')       = fromList [r, r']
uses (PopcountRR _ _ r') = singleton r'
uses (NegR _ r)          = singleton r
uses _                   = IS.empty

defs :: X86 AbsReg ann -> IS.IntSet
defs (MovRA _ r _)      = singleton r
defs (MovRR _ r _)      = singleton r
defs (MovRRLower _ r _) = singleton r
defs (MovRC _ r _)      = singleton r
defs (MovRCBool _ r _)  = singleton r
defs (MovRCi8 _ r _)    = singleton r
defs (MovRWord _ r _)   = singleton r
defs (AddRR _ r _)      = singleton r
defs (SubRR _ r _)      = singleton r
defs (ImulRR _ r _)     = singleton r
defs (AddRC _ r _)      = singleton r
defs (SubRC _ r _)      = singleton r
defs (XorRR _ r _)      = singleton r
defs (MovRL _ r _)      = singleton r
defs (LShiftRRR _ r _)  = singleton r
defs (PopReg _ r)       = singleton r
defs (LShiftLRR _ r _)  = singleton r
defs (AShiftRRR _ r _)  = singleton r
defs (AndRR _ r _)      = singleton r
defs (OrRR _ r _)       = singleton r
defs (PopcountRR _ r _) = singleton r
defs (NegR _ r)         = singleton r
defs (MovRCTag _ r _)   = singleton r
  defs for IdivR & c. ?
defs _                  = IS.empty

next :: [X86 AbsReg ()] -> FreshM ([Int] -> [Int], [X86 AbsReg ControlAnn])
next asms = do
    nextAsms <- addControlFlow asms
    case nextAsms of
        []      -> pure (id, [])
        (asm:_) -> pure ((node (ann asm) :), nextAsms)

-- | Construct map assigning labels to their node name.
broadcasts :: [X86 reg ()] -> FreshM [X86 reg ()]
broadcasts [] = pure []
broadcasts (asm@(Label _ l):asms) = do
    { i <- getFresh
    ; broadcast i l
    ; (asm :) <$> broadcasts asms
    }
broadcasts (asm:asms) = (asm :) <$> broadcasts asms

 seems to pretty clearly be faster
 map of labels by node
 TODO: map int to asm

 This is kinda sus but it allows us to use an 'IntSet' for liveness analysis.
 | Annotate instructions with a unique node name and a list of all possible
 destinations.
 TODO: is this what's wanted?
 TODO?
 | Construct map assigning labels to their node name.
module Kempe.Asm.X86.ControlFlow ( mkControlFlow
                                 , ControlAnn (..)
                                 ) where

import           Control.Monad.State.Strict (State, evalState, gets, modify)
import           Data.Bifunctor             (first, second)
import           Data.Functor               (($>))
import qualified Data.IntSet                as IS
import qualified Data.Map                   as M
import           Data.Semigroup             ((<>))
import           Kempe.Asm.Type
import           Kempe.Asm.X86.Type


runFreshM :: FreshM a -> a
runFreshM = flip evalState (0, mempty)

mkControlFlow :: [X86 AbsReg ()] -> [X86 AbsReg ControlAnn]
mkControlFlow instrs = runFreshM (broadcasts instrs *> addControlFlow instrs)

getFresh :: FreshM Int
getFresh = gets fst <* modify (first (+1))

lookupLabel :: Label -> FreshM Int
lookupLabel l = gets (M.findWithDefault (error "Internal error in control-flow graph: node label not in map.") l . snd)

broadcast :: Int -> Label -> FreshM ()
broadcast i l = modify (second (M.insert l i))

singleton :: AbsReg -> IS.IntSet
singleton = maybe IS.empty IS.singleton . toInt

  | Make sure 8 - bit and 64 - bit registers have no overlap .
  Also ca n't be called on abstract registers i.e. ' DataPointer ' or ' CArg1 ' .
toInt :: AbsReg -> Maybe Int
toInt (AllocReg64 i) = Just i
toInt (AllocReg8 i)  = Just i
toInt _              = Nothing

fromList :: [AbsReg] -> IS.IntSet
fromList = foldMap singleton

addrRegs :: Addr AbsReg -> IS.IntSet
addrRegs (Reg r)              = singleton r
addrRegs (AddrRRPlus r r')    = fromList [r, r']
addrRegs (AddrRCPlus r _)     = singleton r
addrRegs (AddrRCMinus r _)    = singleton r
addrRegs (AddrRRScale r r' _) = fromList [r, r']

addControlFlow :: [X86 AbsReg ()] -> FreshM [X86 AbsReg ControlAnn]
addControlFlow [] = pure []
addControlFlow ((Label _ l):asms) = do
    { i <- lookupLabel l
    ; (f, asms') <- next asms
    ; pure (Label (ControlAnn i (f []) IS.empty IS.empty) l : asms')
    }
addControlFlow ((Je _ l):asms) = do
    { i <- getFresh
    ; (f, asms') <- next asms
    ; pure (Je (ControlAnn i (f [l_i]) IS.empty IS.empty) l : asms')
    }
addControlFlow ((Jl _ l):asms) = do
    { i <- getFresh
    ; (f, asms') <- next asms
    ; l_i <- lookupLabel l
    ; pure (Jl (ControlAnn i (f [l_i]) IS.empty IS.empty) l : asms')
    }
addControlFlow ((Jle _ l):asms) = do
    { i <- getFresh
    ; (f, asms') <- next asms
    ; l_i <- lookupLabel l
    ; pure (Jle (ControlAnn i (f [l_i]) IS.empty IS.empty) l : asms')
    }
addControlFlow ((Jne _ l):asms) = do
    { i <- getFresh
    ; (f, asms') <- next asms
    ; l_i <- lookupLabel l
    ; pure (Jne (ControlAnn i (f [l_i]) IS.empty IS.empty) l : asms')
    }
addControlFlow ((Jge _ l):asms) = do
    { i <- getFresh
    ; (f, asms') <- next asms
    ; l_i <- lookupLabel l
    ; pure (Jge (ControlAnn i (f [l_i]) IS.empty IS.empty) l : asms')
    }
addControlFlow ((Jg _ l):asms) = do
    { i <- getFresh
    ; (f, asms') <- next asms
    ; l_i <- lookupLabel l
    ; pure (Jg (ControlAnn i (f [l_i]) IS.empty IS.empty) l : asms')
    }
addControlFlow ((Jump _ l):asms) = do
    { i <- getFresh
    ; nextAsms <- addControlFlow asms
    ; l_i <- lookupLabel l
    ; pure (Jump (ControlAnn i [l_i] IS.empty IS.empty) l : nextAsms)
    }
addControlFlow ((Call _ l):asms) = do
    { i <- getFresh
    ; nextAsms <- addControlFlow asms
    ; l_i <- lookupLabel l
    ; pure (Call (ControlAnn i [l_i] IS.empty IS.empty) l : nextAsms)
    }
addControlFlow (Ret{}:asms) = do
    { i <- getFresh
    ; nextAsms <- addControlFlow asms
    ; pure (Ret (ControlAnn i [] IS.empty IS.empty) : nextAsms)
    }
addControlFlow (asm:asms) = do
    { i <- getFresh
    ; (f, asms') <- next asms
    ; pure ((asm $> ControlAnn i (f []) (uses asm) (defs asm)) : asms')
    }

uses :: X86 AbsReg ann -> IS.IntSet
uses (PushReg _ r)       = singleton r
uses (PushMem _ a)       = addrRegs a
uses (PopMem _ a)        = addrRegs a
uses (MovRA _ _ a)       = addrRegs a
uses (MovAR _ a r)       = singleton r <> addrRegs a
uses (MovRR _ _ r)       = singleton r
uses (MovRRLower _ _ r)  = singleton r
uses (AddRR _ r r')      = fromList [r, r']
uses (SubRR _ r r')      = fromList [r, r']
uses (ImulRR _ r r')     = fromList [r, r']
uses (AddRC _ r _)       = singleton r
uses (SubRC _ r _)       = singleton r
uses (AddAC _ a _)       = addrRegs a
uses (MovABool _ a _)    = addrRegs a
uses (MovAC  _ a _)      = addrRegs a
uses (MovACi8 _ a _)     = addrRegs a
uses (XorRR _ r r')      = fromList [r, r']
uses (CmpAddrReg _ a r)  = singleton r <> addrRegs a
uses (CmpRegReg _ r r')  = fromList [r, r']
uses (CmpRegBool _ r _)  = singleton r
uses (CmpAddrBool _ a _) = addrRegs a
uses (LShiftLRR _ r r')  = fromList [r, r']
uses (LShiftRRR _ r r')  = fromList [r, r']
uses (AShiftRRR _ r r')  = fromList [r, r']
uses (MovRCi8 _ r _)     = singleton r
uses (MovACTag _ a _)    = addrRegs a
uses (IdivR _ r)         = singleton r
uses (DivR _ r)          = singleton r
uses (AndRR _ r r')      = fromList [r, r']
uses (OrRR _ r r')       = fromList [r, r']
uses (PopcountRR _ _ r') = singleton r'
uses (NegR _ r)          = singleton r
uses _                   = IS.empty

defs :: X86 AbsReg ann -> IS.IntSet
defs (MovRA _ r _)      = singleton r
defs (MovRR _ r _)      = singleton r
defs (MovRRLower _ r _) = singleton r
defs (MovRC _ r _)      = singleton r
defs (MovRCBool _ r _)  = singleton r
defs (MovRCi8 _ r _)    = singleton r
defs (MovRWord _ r _)   = singleton r
defs (AddRR _ r _)      = singleton r
defs (SubRR _ r _)      = singleton r
defs (ImulRR _ r _)     = singleton r
defs (AddRC _ r _)      = singleton r
defs (SubRC _ r _)      = singleton r
defs (XorRR _ r _)      = singleton r
defs (MovRL _ r _)      = singleton r
defs (LShiftRRR _ r _)  = singleton r
defs (PopReg _ r)       = singleton r
defs (LShiftLRR _ r _)  = singleton r
defs (AShiftRRR _ r _)  = singleton r
defs (AndRR _ r _)      = singleton r
defs (OrRR _ r _)       = singleton r
defs (PopcountRR _ r _) = singleton r
defs (NegR _ r)         = singleton r
defs (MovRCTag _ r _)   = singleton r
  defs for IdivR & c. ?
defs _                  = IS.empty

next :: [X86 AbsReg ()] -> FreshM ([Int] -> [Int], [X86 AbsReg ControlAnn])
next asms = do
    nextAsms <- addControlFlow asms
    case nextAsms of
        []      -> pure (id, [])
        (asm:_) -> pure ((node (ann asm) :), nextAsms)

broadcasts :: [X86 reg ()] -> FreshM [X86 reg ()]
broadcasts [] = pure []
broadcasts (asm@(Label _ l):asms) = do
    { i <- getFresh
    ; broadcast i l
    ; (asm :) <$> broadcasts asms
    }
broadcasts (asm:asms) = (asm :) <$> broadcasts asms
