packages feed

datafix-0.0.0.2: examples/Analyses/AdHocStrAnal.hs

module Analyses.AdHocStrAnal (analyse) where

import           Algebra.Lattice
import           Analyses.StrAnal.Arity
import           Analyses.StrAnal.Strictness

import           CoreSyn
import           Id
import           Var
import           VarEnv

analyse :: CoreExpr -> StrLattice
analyse e = analExpr emptyVarEnv e 0

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

analExpr :: VarEnv StrType -> CoreExpr -> Arity -> StrLattice
analExpr env expr arity =
  case expr of
    Lit _       -> emptyStrLattice
    Type _      -> emptyStrLattice
    -- Coercions are irrelevant to Strictness Analysis:
    -- 'emptyStrLattice' is already the 'top' element,
    -- so it's a safe approximation.
    Coercion _ -> emptyStrLattice
    Tick _ e    -> analExpr env e arity
    Cast e _ -> analExpr env e arity
    App f a ->
      let
        StrLattice (fTy, fAnns) = analExpr env f (arity + 1)
        (argStr, fTy') = overArgs unconsArgStr fTy
        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) = analExpr env a argArity
      in mkStrLattice (aTy `bothStrType` fTy') (fAnns \/ aAnns)
    Var id_
      | isLocalId id_ ->
          let
            rhsType = case lookupVarEnv env id_ of
              Just ty
                -- 'ty' is a safe approximation for a call with 'idArity' at
                -- minimum.
                -- Note that 'Arity' is 'Op' ordered.
                | arity <= Arity (idArity id_) -> ty
              _ -> emptyStrType
          in mkStrLattice (unitStrType id_ (Strict arity) `bothStrType` rhsType) emptyAnnotations
      | otherwise -> emptyStrLattice
    Lam id_ body
      | isTyVar id_ -> analExpr env body arity
      | otherwise ->
          let
            StrLattice (ty1, anns) = analExpr env body (0 /\ (arity-1))
            (argStr, ty2) = peelFV id_ ty1
            anns' = annotate id_ argStr anns
            ty3 = modifyArgs (consArgStr argStr) ty2
            ty4 = applyWhen (arity == 0) lazifyStrType ty3
          in mkStrLattice ty4 anns'
    Case scrut bndr _ alts ->
      let
        transferAlt (_, bndrs, alt) =
          peelAndAnnotateFVs bndrs (analExpr env alt arity)
        StrLattice (altTy, altAnns) =
          peelAndAnnotateFV bndr . joins . map transferAlt $ alts
        StrLattice (scrutTy, scrutAnns) = analExpr env scrut 0
      in mkStrLattice (scrutTy `bothStrType` altTy) (scrutAnns \/ altAnns)
    Let bind body ->
      let
        -- we assume a single call with `idArity` for our approximation
        (rhsAnns, env') = case bind of
          NonRec id_ rhs
            | StrLattice (ty, anns) <- analExpr env rhs (Arity (idArity id_))
            -> (anns, extendVarEnv env id_ ty)
          Rec binds  -> fixBinds env binds
        bodyLatt = analExpr env' body arity
        StrLattice (bodyTy, bodyAnns) = peelAndAnnotateFVs (bindersOf bind) bodyLatt
      in mkStrLattice bodyTy (bodyAnns \/ rhsAnns)

fixBinds :: VarEnv StrType -> [(Id, CoreExpr)] -> (Annotations, VarEnv StrType)
fixBinds env binds = mergeWithLatts stableLatts
  where
    mergeWithLatts :: [StrLattice] -> (Annotations, VarEnv StrType)
    mergeWithLatts latts = foldr merger (emptyAnnotations, env) (zip binds latts)

    merger :: ((Id, CoreExpr), StrLattice) -> (Annotations, VarEnv StrType) -> (Annotations, VarEnv StrType)
    merger ((id_, _), StrLattice (ty, anns)) (restAnns, env') =
      (restAnns \/ anns, extendVarEnv env' id_ ty)

    latts0 :: [StrLattice]
    latts0 = map (const bottom) binds

    approximations :: [[StrLattice]]
    approximations = iterate (iter . snd . mergeWithLatts) latts0

    stable :: ([StrLattice], [StrLattice]) -> Bool
    stable (old, new) = map strType old == map strType new

    stableLatts :: [StrLattice]
    stableLatts = snd . head . filter stable $ zip approximations (tail approximations)

    iter env' = snd (foldr iterBind (env', []) binds)

    iterBind (id_, rhs) (env', latts) =
      let
        latt = analExpr env' rhs (Arity (idArity id_))
        env'' = extendVarEnv env' id_ (strType latt)
      in (env'', latt:latts)