packages feed

futhark-0.15.3: src/Futhark/Optimise/InliningDeadFun.hs

{-# LANGUAGE FlexibleContexts #-}
-- | This module implements a compiler pass for inlining functions,
-- then removing those that have become dead.
module Futhark.Optimise.InliningDeadFun
  ( inlineFunctions
  , inlineConstants
  , removeDeadFunctions
  )
  where

import Control.Monad.Identity
import Data.List (partition)
import Data.Loc
import Data.Maybe
import qualified Data.Map.Strict as M
import qualified Data.Set as S

import Futhark.Representation.SOACS
import Futhark.Representation.SOACS.Simplify (simpleSOACS, simplifyFun)
import Futhark.Optimise.CSE
import Futhark.Transform.CopyPropagate (copyPropagateInFun)
import Futhark.Transform.Rename
import Futhark.Analysis.CallGraph
import Futhark.Binder
import Futhark.Pass

aggInlineFunctions :: MonadFreshNames m =>
                      CallGraph -> [FunDef SOACS] -> m [FunDef SOACS]
aggInlineFunctions cg =
  fmap (filter keep) . recurse 0 . filter isFunInCallGraph
  where isFunInCallGraph fundec =
          isJust $ M.lookup (funDefName fundec) cg

        constfuns =
          S.fromList $ M.keys $ M.filter (==ConstFun) $ mconcat $ M.elems cg

        fdmap fds =
          M.fromList $ zip (map funDefName fds) fds

        noCallsTo :: (Name -> Bool) -> FunDef SOACS -> Bool
        noCallsTo interesting fundec =
          case M.lookup (funDefName fundec) cg of
            Just calls -> not $ any interesting (M.keys calls)
            _ -> False

        -- The inverse rate at which we perform full simplification
        -- after inlining.  For the other steps we just do copy
        -- propagation.  The rate here has been determined
        -- heuristically and is probably not optimal for any given
        -- program.
        simplifyRate :: Int
        simplifyRate = 4

        -- We apply simplification after every round of inlining,
        -- because it is more efficient to shrink the program as soon
        -- as possible, rather than wait until it has balooned after
        -- full inlining.
        recurse i funs = do
          let remaining = S.fromList $ map funDefName funs
              (to_be_inlined, maybe_inline_in) =
                partition (noCallsTo (`S.member` remaining)) funs
              (not_to_inline_in, to_inline_in) =
                partition (noCallsTo
                           (`elem` map funDefName to_be_inlined))
                maybe_inline_in
              (not_actually_inlined, to_be_inlined') =
                partition keep to_be_inlined
          if null to_be_inlined
            then return funs
            else do let simplify fd
                          | i `rem` simplifyRate == 0 ||
                            funDefName fd `S.member` constfuns =
                              copyPropagateInFun simpleSOACS =<<
                              performCSEOnFunDef True <$> simplifyFun fd
                          | otherwise =
                              copyPropagateInFun simpleSOACS fd

                    let onFun = simplify <=< renameFun .
                                doInlineInCaller (fdmap to_be_inlined') False
                    to_inline_in' <- mapM onFun to_inline_in
                    (not_actually_inlined<>) <$>
                      recurse (i+1) (not_to_inline_in <> to_inline_in')

        keep fd =
          isJust (funDefEntryPoint fd)
          || callsRecursive fd
          || expensiveConstant fd

        expensiveConstant fd =
          funDefName fd `S.member` constfuns &&
          not (null (bodyStms (funDefBody fd)))

        callsRecursive fd = maybe False (any recursive . M.keys) $
                            M.lookup (funDefName fd) cg

        recursive fname = case M.lookup fname cg of
                            Just calls -> fname `M.member` calls
                            Nothing -> False

-- | @doInlineInCaller constf fdmap caller@ inlines in @calleer@
-- the functions in @fdmap@ that are called as @constf@. At this
-- point the preconditions are that if @fdmap@ is not empty, and,
-- more importantly, the functions in @fdmap@ do not call any
-- other functions. Further extensions that transform a tail-recursive
-- function to a do or while loop, should do the transformation first
-- and then do the inlining.
doInlineInCaller :: M.Map Name (FunDef SOACS) -> Bool -> FunDef SOACS
                 -> FunDef SOACS
doInlineInCaller fdmap always_reshape (FunDef entry name rtp args body) =
  let body' = inlineInBody fdmap always_reshape body
  in FunDef entry name rtp args body'

inlineFunction :: Bool
               -> Pattern
               -> StmAux attr
               -> [(SubExp, Diet)]
               -> (ConstFun, Safety, SrcLoc, [SrcLoc])
               -> FunDef SOACS
               -> [Stm]
inlineFunction always_reshape pat aux args (_,safety,loc,locs) fun =
  param_stms <> body_stms <> res_stms
  where param_names =
          map paramName $ funDefParams fun

        param_stms =
          zipWith (reshapeIfNecessary param_names)
          (map paramIdent $ funDefParams fun) (map fst args)

        body_stms =
          stmsToList $
          addLocations safety (filter notNoLoc (loc:locs)) $
          bodyStms $ funDefBody fun

        res_stms =
          certify (stmAuxCerts aux) <$>
          zipWith (reshapeIfNecessary (patternNames pat))
          (patternIdents pat) (bodyResult $ funDefBody fun)

        reshapeIfNecessary dim_names ident se
          | t@Array{} <- identType ident,
            always_reshape || any (`elem` dim_names) (subExpVars $ arrayDims t),
            Var v <- se =
              mkLet [] [ident] $ shapeCoerce (arrayDims t) v
          | otherwise =
              mkLet [] [ident] $ BasicOp $ SubExp se

        notNoLoc = (/=NoLoc) . locOf

inlineInBody :: M.Map Name (FunDef SOACS) -> Bool -> Body -> Body
inlineInBody fdmap always_reshape = onBody
  where inline (Let pat aux (Apply fname args _ what) : rest)
          | Just fd <- M.lookup fname fdmap =
              inlineFunction always_reshape pat aux args what fd
              <> inline rest
        inline (stm : rest) =
          onStm stm : inline rest
        inline [] = mempty

        onBody (Body attr stms res) =
          Body attr (stmsFromList $ inline (stmsToList stms)) res

        onStm (Let pat aux e) =
          Let pat aux $ mapExp inliner e

        inliner =
          identityMapper { mapOnBody = const $ return . onBody
                         , mapOnOp = return . onSOAC
                         }

        onSOAC =
          runIdentity . mapSOACM identitySOACMapper
          { mapOnSOACLambda = return . onLambda }

        onLambda (Lambda params body ret) =
          Lambda params (onBody body) ret

addLocations :: Safety -> [SrcLoc] -> Stms SOACS -> Stms SOACS
addLocations caller_safety more_locs = fmap onStm
  where onStm stm = stm { stmExp = onExp $ stmExp stm }
        onExp (Apply fname args t (constf, safety, loc,locs)) =
          Apply fname args t (constf, min caller_safety safety, loc,locs++more_locs)
        onExp (BasicOp (Assert cond desc (loc,locs))) =
          case caller_safety of
            Safe -> BasicOp $ Assert cond desc (loc,locs++more_locs)
            Unsafe -> BasicOp $ SubExp $ Constant Checked
        onExp (Op soac) = Op $ runIdentity $ mapSOACM
                          identitySOACMapper { mapOnSOACLambda = return . onLambda
                                             } soac
        onExp e = mapExp identityMapper { mapOnBody = const $ return . onBody
                                        } e
        onBody body =
          body { bodyStms = addLocations caller_safety more_locs $ bodyStms body }
        onLambda :: Lambda -> Lambda
        onLambda lam = lam { lambdaBody = onBody $ lambdaBody lam }

-- | Inline 'NotConstFun' functions and remove the resulting dead functions.
inlineFunctions :: Pass SOACS SOACS
inlineFunctions =
  Pass { passName = "Inline functions"
       , passDescription = "Inline and remove resulting dead functions."
       , passFunction = pass
       }
  where pass prog = do
          let cg = buildCallGraph prog
          Prog <$> aggInlineFunctions cg (progFuns prog)

aggInlineConstants :: [FunDef SOACS] -> [FunDef SOACS]
aggInlineConstants orig_fds =
  map inlineInEntry $ filter (isJust . funDefEntryPoint) orig_fds
  where fdmap = M.fromList $ zip (map funDefName orig_fds) orig_fds

        inlineInEntry fd =
          fd { funDefBody = constsInBody mempty $ funDefBody fd }

        constsInBody prev body =
          body { bodyStms = constsInStms prev (bodyStms body) }

        constsInStms prev stms =
          case stmsHead stms of
            Nothing -> mempty

            Just (Let pat aux (Apply fname args _ prop),
                  stms')
              | Just ses <- M.lookup fname prev ->
                  stmsFromList
                  (zipWith reshapeResult (patternIdents pat) ses)
                  <> constsInStms prev stms'

              | Just fd <- M.lookup fname fdmap ->
                  let stm_stms =
                        inlineFunction True pat aux args prop fd
                      prev' =
                        M.insert fname (map Var $ patternNames pat) prev
                  in constsInStms prev' $ stmsFromList stm_stms <> stms'

            Just (stm, stms') ->
              oneStm stm <> constsInStms prev stms'

        reshapeResult ident se
          | t@Array{} <- identType ident,
            Var v <- se =
              mkLet [] [ident] $ shapeCoerce (arrayDims t) v
          | otherwise =
              mkLet [] [ident] $ BasicOp $ SubExp se

-- | Inline 'ConstFun' functions and remove the resulting dead functions.
inlineConstants :: Pass SOACS SOACS
inlineConstants =
  Pass { passName = "Inline constants"
       , passDescription = "Inline and remove dead constants."
       , passFunction = pass
       }
  where pass prog = return $ Prog $ aggInlineConstants $ progFuns prog

-- | @removeDeadFunctions prog@ removes the functions that are unreachable from
-- the main function from the program.
removeDeadFunctions :: Pass SOACS SOACS
removeDeadFunctions =
  Pass { passName = "Remove dead functions"
       , passDescription = "Remove the functions that are unreachable from entry points"
       , passFunction = return . pass
       }
  where pass prog =
          let cg        = buildCallGraph prog
              live_funs = filter (isFunInCallGraph cg) (progFuns prog)
          in Prog live_funs
        isFunInCallGraph cg fundec = isJust $ M.lookup (funDefName fundec) cg