packages feed

datafix-0.0.0.1: examples/Analyses/StrAnal/Analysis.hs

{-# LANGUAGE ScopedTypeVariables #-}
-- This is so that the specialisation of transferFunctionAlg gets inlined.
{-# OPTIONS_GHC -funfolding-creation-threshold=999999 #-}
--{-# OPTIONS_GHC -ddump-simpl -ddump-to-file -dsuppress-all #-}

-- | This module defines a strictness analysis in the style of GHC's
-- projection-based backwards analysis by defining a 'transferFunctionAlg'
-- that is passed on to @Analyses.Templates.LetDn.'buildProblem'@,
-- yielding a 'DataFlowProblem' to be solved by @Datafix.'solveProblem'@.
module Analyses.StrAnal.Analysis (analyse) where

import           Algebra.Lattice
import           Analyses.StrAnal.Arity
import           Analyses.StrAnal.Strictness
import           Analyses.Syntax.CoreSynF
import           Analyses.Templates.LetDn
import           Control.Monad               (foldM)
import           Datafix.Worklist            (IterationBound (..),
                                              evalDenotation)

import           CoreSyn
import           Id
import           Var
import           VarEnv

analyse :: CoreExpr -> StrLattice
analyse expr = evalDenotation (buildDenotation transferFunctionAlg expr) NeverAbort 0

applyWhen :: Bool -> (a -> a) -> a -> a
applyWhen True f  = f
applyWhen False _ = id

-- | This specifies the strictness as a 'TransferAlgebra'. Note the absence
-- of any recursion! That's all abstracted into
-- @Analyses.Tempaltes.LetDn.'buildProblem'@, so that this function definition
-- is completely compositional: It is only concerned with peeling off a single
-- layer of the 'CoreExprF' and interpret that in terms of the
-- transfer function over the @Arity -> StrLattice@ 'Domain'.
--
-- Because there is no explicit fixpointing going on, the resulting analysis
-- logic is clear and to the point.
transferFunctionAlg :: TransferAlgebra (Arity -> StrLattice)
transferFunctionAlg _ _ env expr arity =
  case expr of
    LitF _       -> pure emptyStrLattice
    TypeF _      -> pure emptyStrLattice
    -- Coercions are irrelevant to Strictness Analysis:
    -- 'emptyStrLattice' is already the 'top' element,
    -- so it's a safe approximation.
    CoercionF _ -> pure emptyStrLattice
    TickF _ e    -> e arity
    CastF e _ -> e arity
    AppF f a -> do
      StrLattice (fTy, fAnns) <- f (arity + 1)
      let (argStr, fTy') = overArgs unconsArgStr fTy
      let argArity =
            case argStr of
              -- It's unfortunate that we don't have the type available to
              -- trim this... But it doesn't hurt either.
              HyperStrict -> Arity maxBound
              Lazy        -> 0
              Strict n    -> n
      StrLattice (aTy, aAnns) <- a argArity
      pure (mkStrLattice (aTy `bothStrType` fTy') (fAnns \/ aAnns))
    VarF id_
      | isLocalId id_ -> do
          rhsType <- case lookupVarEnv env id_ of
            Just denotation -> strType <$> denotation arity
            Nothing         -> pure emptyStrType
          pure (mkStrLattice (unitStrType id_ (Strict arity) `bothStrType` rhsType) emptyAnnotations)
      | otherwise -> pure emptyStrLattice
    LamF id_ body
      | isTyVar id_ -> body arity
      | otherwise -> do
          StrLattice (ty1, anns) <- body (0 /\ (arity-1))
          let (argStr, ty2) = peelFV id_ ty1
          let anns' = annotate id_ argStr anns
          let ty3 = modifyArgs (consArgStr argStr) ty2
          let ty4 = applyWhen (arity == 0) lazifyStrType ty3
          pure (mkStrLattice ty4 anns')
    CaseF scrut bndr _ alts -> do
      let transferAlt (_, bndrs, transfer) = do
            latt <- transfer arity
            pure (peelAndAnnotateFVs bndrs latt)
      StrLattice (altTy, altAnns) <-
        peelAndAnnotateFV bndr . joins <$> mapM transferAlt alts
      StrLattice (scrutTy, scrutAnns) <- scrut 0
      pure (mkStrLattice (scrutTy `bothStrType` altTy) (scrutAnns \/ altAnns))
    LetF bind body -> do
      let transferBinder (StrLattice (ty, anns)) (id_, transfer) = do
            -- We do this only for annotations.
            -- Strictness on free variables was unleashed
            -- at call sites, now we only have to
            -- 'transfer' with the minimum incoming arity.
            -- Well, actually the minimum possible arity
            -- for which we annotate is 'idArity'.
            -- This is OK as long as the function is only
            -- called through the wrapper and as long as
            -- this wrapper is only inlined when fully
            -- saturated.
            -- Otherwise, to account for unsaturated calls,
            -- we'd always have to assume incoming arity 0
            -- for annotations, which wouldn't allow us to
            -- unbox any arguments.
            let (str, ty') = peelFV id_ ty
            let anns' = annotate id_ str anns
            let oldArity = Arity (idArity id_)
            let safeArity
                  | Strict n <- str = n
                  | otherwise = 0
            let annotationArity = oldArity /\ safeArity
            StrLattice (_, rhsAnns) <- transfer annotationArity
            pure (mkStrLattice ty' (anns' \/ rhsAnns))
      latt <- body arity
      foldM transferBinder latt (flattenBindsF [bind])