{-# LANGUAGE ViewPatterns, NoMonomorphismRestriction #-}
{-|

This analysis identifies the (memory) effects that functions have on
the scalar components of their arguments.

Only pointer parameters are interesting because only their effects can
escape the callee.  Effects are currently restricted to increments and
decrements of integral types.  The affected memory can be a struct
member; the effects are described in terms of abstract AccessPaths.

This is a must analysis.  Effects are only reported if they *MUST*
occur (modulo non-termination style effects like calls to exit or
infinite loops).

Currently, sequential effects are not composed and nothing useful will
be reported.

-}
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

-- | The types of effects tracked by this analysis.  This can be expanded
-- as the analysis becomes more sophisticated (it could include general
-- affine relations or even relate arguments to each other).
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

meet :: ScalarInfo -> ScalarInfo -> ScalarInfo
meet SITop s = s
meet s SITop = s
meet (SI s1) (SI s2) = SI (HM.unionWith mergeEffect s1 s2)
  where
    -- | If there is an entry in both maps, it must be the same to be
    -- retained.
    mergeEffect e1 e2 = if e1 == e2 then e1 else Nothing

-- For each function, initialize all arguments to Nothing
scalarEffectAnalysis :: (Monad m, HasCFG funcLike, HasFunction funcLike)
                        => funcLike
                        -> ScalarEffectResult
                        -> m ScalarEffectResult
scalarEffectAnalysis funcLike summ = do
  let cfg = getCFG funcLike
      analysis = fwdDataflowAnalysis SITop meet scalarTransfer

  localRes <- dataflow cfg analysis SITop
  let xi = case dataflowResult localRes of
        SITop -> HM.empty
        SI m -> HM.foldlWithKey' discardNothings HM.empty m
  return $! HM.union xi 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

{-# ANN module "HLint: ignore Use if" #-}