module LLVM.Analysis.ScalarEffects (
  ScalarEffectResult,
  ScalarEffect(..),
  scalarEffectAnalysis
  ) where
import Control.DeepSeq
import Data.HashMap.Strict ( HashMap )
import qualified Data.HashMap.Strict as HM
import LLVM.Analysis
import LLVM.Analysis.AccessPath
import LLVM.Analysis.CFG
import LLVM.Analysis.Dataflow
data ScalarEffect = EffectAdd1 AbstractAccessPath
                  | EffectSub1 AbstractAccessPath
                  deriving (Eq)
instance NFData ScalarEffect where
  rnf e@(EffectAdd1 ap) = ap `deepseq` e `seq` ()
  rnf e@(EffectSub1 ap) = ap `deepseq` e `seq` ()
type ScalarEffectResult = HashMap Argument ScalarEffect
data ScalarInfo = SI (HashMap Argument (Maybe ScalarEffect))
                | SITop
instance Eq ScalarInfo where
  (SI s1) == (SI s2) = s1 == s2
  SITop == SITop = True
  _ == _ = False
instance MeetSemiLattice ScalarInfo where
  meet SITop s = s
  meet s SITop = s
  meet (SI s1) (SI s2) = SI (HM.unionWith mergeEffect s1 s2)
    where
      
      
      mergeEffect e1 e2 = if e1 == e2 then e1 else Nothing
instance BoundedMeetSemiLattice ScalarInfo where
  top = SITop
instance (Monad m) => DataflowAnalysis m ScalarInfo where
  transfer = scalarTransfer
scalarEffectAnalysis :: (Monad m, HasCFG funcLike, HasFunction funcLike)
                        => funcLike
                        -> ScalarEffectResult
                        -> m ScalarEffectResult
scalarEffectAnalysis funcLike summ = do
  let cfg = getCFG funcLike
      f = getFunction funcLike
      s0 = SI $ HM.fromList (zip (functionParameters f) (repeat Nothing))
  localRes <- forwardDataflow s0 cfg
  let exitInsts = filter (instructionReachable cfg) (functionExitInstructions f)
      exitInfo = meets $ map (dataflowResult localRes) exitInsts
      exitInfo' = case exitInfo of
        SITop -> HM.empty
        SI m -> HM.foldlWithKey' discardNothings HM.empty m
  return $! HM.union exitInfo' summ
discardNothings :: HashMap Argument ScalarEffect
                   -> Argument
                   -> Maybe ScalarEffect
                   -> HashMap Argument ScalarEffect
discardNothings acc _ Nothing = acc
discardNothings acc a (Just e) = HM.insert a e acc
scalarTransfer :: (Monad m) => ScalarInfo -> Instruction -> m ScalarInfo
scalarTransfer si i =
  case i of
    AtomicRMWInst { atomicRMWOperation = AOAdd
                  , atomicRMWValue =
      (valueContent -> ConstantC ConstantInt { constantIntValue = 1 })} ->
      recordIfAffectsArgument EffectAdd1 i si
    AtomicRMWInst { atomicRMWOperation = AOAdd
                  , atomicRMWValue =
      (valueContent -> ConstantC ConstantInt { constantIntValue = 1 })} ->
      recordIfAffectsArgument EffectSub1 i si
    AtomicRMWInst { atomicRMWOperation = AOSub
                  , atomicRMWValue =
      (valueContent -> ConstantC ConstantInt { constantIntValue = 1 })} ->
      recordIfAffectsArgument EffectSub1 i si
    AtomicRMWInst { atomicRMWOperation = AOSub
                  , atomicRMWValue =
      (valueContent -> ConstantC ConstantInt { constantIntValue = 1 })} ->
      recordIfAffectsArgument EffectAdd1 i si
    StoreInst { storeAddress = sa, storeValue = sv } ->
      case isNonAtomicAdd sa sv of
        False ->
          case isNonAtomicSub sa sv of
            False -> return si
            True -> recordIfAffectsArgument EffectSub1 i si
        True -> recordIfAffectsArgument EffectAdd1 i si
    _ -> return si
isNonAtomicSub :: (IsValue a) => Value -> a -> Bool
isNonAtomicSub sa sv =
  case valueContent sv of
    InstructionC AddInst {
      binaryLhs = (valueContent -> ConstantC ConstantInt { constantIntValue = 1 }),
      binaryRhs = (valueContent -> InstructionC LoadInst { loadAddress = la }) } ->
      sa == la
    InstructionC AddInst {
      binaryRhs = (valueContent -> ConstantC ConstantInt { constantIntValue = 1 }),
      binaryLhs = (valueContent -> InstructionC LoadInst { loadAddress = la }) } ->
      sa == la
    InstructionC SubInst {
      binaryRhs = (valueContent -> ConstantC ConstantInt { constantIntValue = 1 }),
      binaryLhs = (valueContent -> InstructionC LoadInst { loadAddress = la }) } ->
      sa == la
    _ -> False
isNonAtomicAdd :: (IsValue a) => Value -> a -> Bool
isNonAtomicAdd sa sv =
  case valueContent sv of
    InstructionC AddInst {
      binaryLhs = (valueContent -> ConstantC ConstantInt { constantIntValue = 1 }),
      binaryRhs = (valueContent -> InstructionC LoadInst { loadAddress = la }) } ->
      sa == la
    InstructionC AddInst {
      binaryRhs = (valueContent -> ConstantC ConstantInt { constantIntValue = 1 }),
      binaryLhs = (valueContent -> InstructionC LoadInst { loadAddress = la }) } ->
      sa == la
    InstructionC SubInst {
      binaryRhs = (valueContent -> ConstantC ConstantInt { constantIntValue = 1 }),
      binaryLhs = (valueContent -> InstructionC LoadInst { loadAddress = la }) } ->
      sa == la
    _ -> False
recordIfAffectsArgument :: (Monad m)
                           => (AbstractAccessPath -> ScalarEffect)
                           -> Instruction
                           -> ScalarInfo
                           -> m ScalarInfo
recordIfAffectsArgument con i si =
  case accessPath i of
    Nothing -> return si
    Just cap ->
      case valueContent' (accessPathBaseValue cap) of
        ArgumentC a ->
          let e = Just $ con (abstractAccessPath cap)
          in case si of
            SITop -> return $! SI $ HM.insert a e HM.empty
            SI m -> return $! SI $ HM.insert a e m
        _ -> return si