futhark-0.15.8: src/Futhark/Analysis/CallGraph.hs
{-# LANGUAGE OverloadedStrings #-}
-- | This module exports functionality for generating a call graph of
-- an Futhark program.
module Futhark.Analysis.CallGraph
( CallGraph
, buildCallGraph
, isFunInCallGraph
, calls
, calledByConsts
, allCalledBy
, findNoninlined
)
where
import Control.Monad.Writer.Strict
import qualified Data.Map.Strict as M
import qualified Data.Set as S
import Data.Maybe (fromMaybe)
import Data.List (foldl')
import Futhark.IR.SOACS
type FunctionTable = M.Map Name (FunDef SOACS)
buildFunctionTable :: Prog SOACS -> FunctionTable
buildFunctionTable = foldl expand M.empty . progFuns
where expand ftab f = M.insert (funDefName f) f ftab
type FunGraph = M.Map Name (S.Set Name)
-- | The call graph is a mapping from a function name, i.e., the
-- caller, to a set of the names of functions called *directly* (not
-- transitively!) by the function.
--
-- We keep track separately of the functions called by constants.
data CallGraph = CallGraph { calledByFuns :: M.Map Name (S.Set Name)
, calledInConsts :: S.Set Name
}
-- | Is the given function known to the call graph?
isFunInCallGraph :: Name -> CallGraph -> Bool
isFunInCallGraph f = M.member f . calledByFuns
-- | Does the first function call the second?
calls :: Name -> Name -> CallGraph -> Bool
calls caller callee =
maybe False (S.member callee) . M.lookup caller . calledByFuns
-- | Is the function called in any of the constants?
calledByConsts :: Name -> CallGraph -> Bool
calledByConsts f = S.member f . calledInConsts
-- | All functions called by this function.
allCalledBy :: Name -> CallGraph -> S.Set Name
allCalledBy f = fromMaybe mempty . M.lookup f . calledByFuns
-- | @buildCallGraph prog@ build the program's call graph.
buildCallGraph :: Prog SOACS -> CallGraph
buildCallGraph prog =
CallGraph fg $ buildFGStms $ progConsts prog
where fg = foldl' (buildFGfun ftable) M.empty entry_points
entry_points = map funDefName $ progFuns prog
ftable = buildFunctionTable prog
-- | @buildCallGraph ftable fg fname@ updates @fg@ with the
-- contributions of function @fname@.
buildFGfun :: FunctionTable -> FunGraph -> Name -> FunGraph
buildFGfun ftable fg fname =
-- Check if function is a non-builtin that we have not already
-- processed.
case M.lookup fname ftable of
Just f | Nothing <- M.lookup fname fg -> do
let callees = buildFGBody $ funDefBody f
fg' = M.insert fname callees fg
-- recursively build the callees
foldl' (buildFGfun ftable) fg' callees
_ -> fg
buildFGStms :: Stms SOACS -> S.Set Name
buildFGStms = mconcat . map (buildFGexp . stmExp) . stmsToList
buildFGBody :: Body -> S.Set Name
buildFGBody = buildFGStms . bodyStms
buildFGexp :: Exp -> S.Set Name
buildFGexp (Apply fname _ _ _) = S.singleton fname
buildFGexp (Op op) = execWriter $ mapSOACM folder op
where folder = identitySOACMapper {
mapOnSOACLambda = \lam -> do tell $ buildFGBody $ lambdaBody lam
return lam
}
buildFGexp e = execWriter $ mapExpM folder e
where folder = identityMapper {
mapOnBody = \_ body -> do tell $ buildFGBody body
return body
}
-- | The set of all functions that are called noinline somewhere.
findNoninlined :: Prog SOACS -> S.Set Name
findNoninlined prog =
foldMap onStm $ foldMap (bodyStms . funDefBody) (progFuns prog) <> progConsts prog
where onStm :: Stm -> S.Set Name
onStm (Let _ aux (Apply fname _ _ _))
| "noinline" `inAttrs` stmAuxAttrs aux =
S.singleton fname
onStm (Let _ _ e) = execWriter $ mapExpM folder e
where folder =
identityMapper
{ mapOnBody = \_ body -> do tell $ foldMap onStm $ bodyStms body
return body
, mapOnOp = mapSOACM
identitySOACMapper
{ mapOnSOACLambda = \lam -> do
tell $ foldMap onStm $ bodyStms $ lambdaBody lam
return lam
}
}