hic-0.0.0.1: src/Language/Cimple/Analysis/ArrayUsageAnalysis.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
module Language.Cimple.Analysis.ArrayUsageAnalysis
( ArrayUsageResult (..)
, ArrayFlavor (..)
, ArrayIdentity (..)
, runArrayUsageAnalysis
) where
import Control.Applicative ((<|>))
import Control.Monad.State.Strict (State, execState)
import qualified Control.Monad.State.Strict as State
import Data.Aeson (ToJSON, ToJSONKey)
import Data.Fix (foldFix)
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Text (Text)
import qualified Data.Text as T
import GHC.Generics (Generic)
import Language.Cimple (Lexeme (..), Node,
NodeF (..))
import qualified Language.Cimple as C
import Language.Cimple.Analysis.AstUtils (parseInteger)
import Language.Cimple.Analysis.TypeSystem (pattern BuiltinType,
Phase (..),
pattern Pointer,
StdType (..),
TypeDescr (..), TypeInfo,
TypeRef (..),
pattern TypeRef,
TypeSystem, lookupType)
import qualified Language.Cimple.Analysis.TypeSystem as TS
import qualified Language.Cimple.Program as Program
data ArrayFlavor
= FlavorHomogeneous -- Only variable indices
| FlavorHeterogeneous -- Only literal indices
| FlavorMixed -- Both literal and variable indices
deriving (Show, Eq, Generic)
instance ToJSON ArrayFlavor
data ArrayIdentity
= GlobalArray Text
| MemberArray Text Text -- StructName, MemberName
| LocalArray Text Text -- FunctionName, VarName
deriving (Show, Eq, Ord, Generic)
instance ToJSON ArrayIdentity
instance ToJSONKey ArrayIdentity
data ArrayUsageResult = ArrayUsageResult
{ aurFlavors :: Map ArrayIdentity ArrayFlavor
, aurAccesses :: Map ArrayIdentity (Set (Maybe Integer))
} deriving (Show, Generic)
instance ToJSON ArrayUsageResult
data AnalysisState = AnalysisState
{ asAccesses :: Map ArrayIdentity (Set (Maybe Integer))
, asTypeSystem :: TypeSystem
, asCurrentFunc :: Maybe Text
, asLocalVars :: [Map Text (TypeInfo 'Global)]
}
type Analyze = State AnalysisState
enterScope :: Analyze ()
enterScope = State.modify $ \s -> s { asLocalVars = Map.empty : asLocalVars s }
exitScope :: Analyze ()
exitScope = State.modify $ \s -> s { asLocalVars = drop 1 (asLocalVars s) }
addVar :: Text -> TypeInfo 'Global -> Analyze ()
addVar name ty = State.modify $ \s ->
case asLocalVars s of
(m:ms) -> s { asLocalVars = Map.insert name ty m : ms }
[] -> s { asLocalVars = [Map.singleton name ty] }
lookupVar :: Text -> Analyze (Maybe (TypeInfo 'Global))
lookupVar name = do
vars <- State.gets asLocalVars
return $ foldl (\acc m -> acc <|> Map.lookup name m) Nothing vars
data Result = Result
{ resAction :: Analyze ()
, resType :: Analyze (Maybe (TypeInfo 'Global))
, resId :: Analyze (Maybe ArrayIdentity)
, resIdx :: Maybe Integer
, resTypeInfo :: TypeInfo 'Global
, resNode :: NodeF (Lexeme Text) Result
}
runArrayUsageAnalysis :: TypeSystem -> Program.Program Text -> ArrayUsageResult
runArrayUsageAnalysis ts program =
let initialState = AnalysisState Map.empty ts Nothing [Map.empty]
finalState = execState (mapM_ (mapM_ traverseNode . snd) (Program.toList program)) initialState
flavors = Map.map categorize (asAccesses finalState)
in ArrayUsageResult flavors (asAccesses finalState)
where
categorize indices =
let hasLiteral = any (\case Just _ -> True; _ -> False) indices
hasVariable = Set.member Nothing indices
in case (hasLiteral, hasVariable) of
(True, True) -> FlavorMixed
(True, False) -> FlavorHeterogeneous
_ -> FlavorHomogeneous
traverseNode :: Node (Lexeme Text) -> Analyze ()
traverseNode = resAction . foldFix alg
alg :: NodeF (Lexeme Text) Result -> Result
alg node = Result
{ resAction = doAction node
, resType = doType node
, resId = doId node
, resIdx = doIdx node
, resTypeInfo = doTypeInfo node
, resNode = node
}
doAction = \case
C.FunctionDefn _ proto body -> do
case resNode proto of
C.FunctionPrototype _ (L _ _ name) params -> do
oldFunc <- State.gets asCurrentFunc
oldVars <- State.gets asLocalVars
let globalScope = case oldVars of
(g:_) -> g
[] -> error "traverseNode: Scope stack empty"
State.modify $ \s -> s { asCurrentFunc = Just name, asLocalVars = [globalScope] }
enterScope
mapM_ registerParam params
resAction body
State.modify $ \s -> s { asCurrentFunc = oldFunc, asLocalVars = oldVars }
_ -> resAction body
C.CompoundStmt stmts -> do
enterScope
mapM_ resAction stmts
exitScope
C.VarDeclStmt r mInit -> do
case resNode r of
C.VarDecl ty (L _ _ name) _ -> do
let t = resTypeInfo ty
addVar name t
mapM_ resAction mInit
_ -> mapM_ resAction mInit
C.ForStmt init' cond step body -> do
enterScope
resAction init'
resAction cond
resAction step
resAction body
exitScope
C.ArrayAccess base idx -> do
resAction base
resAction idx
mId <- resId base
let mIdx = resIdx idx
case mId of
Just ident -> State.modify $ \s ->
s { asAccesses = Map.insertWith Set.union ident (Set.singleton mIdx) (asAccesses s) }
Nothing -> return ()
other -> mapM_ resAction other
doType = \case
C.VarExpr (L _ _ name) -> lookupVar name
C.ParenExpr e -> resType e
C.MemberAccess obj (L _ _ field) -> do
mTy <- resType obj
case mTy of
Just (TypeRef _ (L _ _ tid) _) -> lookupMemberType (TS.templateIdBaseName tid) field
_ -> return Nothing
C.PointerAccess obj (L _ _ field) -> do
mTy <- resType obj
case mTy of
Just (Pointer (TypeRef _ (L _ _ tid) _)) -> lookupMemberType (TS.templateIdBaseName tid) field
Just (TypeRef _ (L _ _ tid) _) -> lookupMemberType (TS.templateIdBaseName tid) field
_ -> return Nothing
_ -> return Nothing
doId = \case
C.VarExpr (L _ _ name) -> do
mFunc <- State.gets asCurrentFunc
return $ Just $ case mFunc of
Just f -> LocalArray f name
Nothing -> GlobalArray name
C.MemberAccess obj (L _ _ field) -> do
mObjTy <- resType obj
case mObjTy of
Just (TypeRef _ (L _ _ tid) _) ->
return $ Just $ MemberArray (TS.templateIdBaseName tid) field
_ -> return Nothing
C.PointerAccess obj (L _ _ field) -> do
mObjTy <- resType obj
case mObjTy of
Just (Pointer (TypeRef _ (L _ _ tid) _)) ->
return $ Just $ MemberArray (TS.templateIdBaseName tid) field
Just (TypeRef _ (L _ _ tid) _) ->
return $ Just $ MemberArray (TS.templateIdBaseName tid) field
_ -> return Nothing
C.ParenExpr e -> resId e
_ -> return Nothing
doIdx = \case
C.LiteralExpr C.Int (L _ _ val) -> parseInteger val
C.ParenExpr e -> resIdx e
_ -> Nothing
doTypeInfo = \case
C.TyStd l -> TS.builtin l
C.TyPointer t -> TS.Pointer (resTypeInfo t)
C.TyStruct (L p t name) -> TS.TypeRef TS.StructRef (L p t (TS.TIdName name)) []
C.TyUserDefined (L p t name) -> TS.TypeRef TS.UnresolvedRef (L p t (TS.TIdName name)) []
_ -> BuiltinType VoidTy
registerParam r = case resNode r of
C.VarDecl ty (L _ _ name) _ -> do
let t = resTypeInfo ty
addVar name t
C.NonNullParam p -> registerParam p
C.NullableParam p -> registerParam p
_ -> return ()
lookupMemberType structName field = do
ts' <- State.gets asTypeSystem
return $ TS.lookupMemberType field =<< TS.lookupType structName ts'