packages feed

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

{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ScopedTypeVariables        #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

module Analyses.StrAnal.Strictness where

import           Algebra.Lattice
import           Data.IntMap.Strict     (IntMap)
import           Data.Maybe             (fromMaybe)
import           Unsafe.Coerce          (unsafeCoerce)

import           Analyses.StrAnal.Arity

import           Coercion
import           Id
import           UniqFM
import           VarEnv

instance Show v => Show (UniqFM v) where
  -- I'd rather use coerce or the UFM constructor, but
  -- that isn't exported.
  show env = show (unsafeCoerce env :: IntMap v)


-- | Captures lower bounds on evaluation cardinality of some variable.
-- E.g.: Is this variable evaluated at least once, and if so, what is the
-- maximum number of arguments it was surely applied to?
data Strictness
  = Lazy         -- ^ Evaluated lazily (possibly not evaluated at all)
  | Strict Arity -- ^ Evaluated strictly (>= 1), called with n args
  | HyperStrict  -- ^ Fully evaluated, a call with maximum arity
  deriving Eq

instance Show Strictness where
  show Lazy        = "L"
  show (Strict n)  = "S(" ++ show n ++ ")"
  show HyperStrict = "B"

instance JoinSemiLattice Strictness where
  Lazy \/ _ = Lazy
  _ \/ Lazy = Lazy
  HyperStrict \/ s = s
  s \/ HyperStrict = s
  Strict n \/ Strict m = Strict (n \/ m)

instance BoundedJoinSemiLattice Strictness where
  bottom = HyperStrict

instance MeetSemiLattice Strictness where
  HyperStrict /\ _ = HyperStrict
  _ /\ HyperStrict = HyperStrict
  Lazy /\ s = s
  s /\ Lazy = s
  Strict n /\ Strict m = Strict (n /\ m)

instance BoundedMeetSemiLattice Strictness where
  top = Lazy

-- | Captures certain divergence through 'Diverges', which allows
-- to assume a 'defaultStr' of 'HyperStrict'.
data Termination
  = Diverges -- ^ Denotes certain divergence
  | Dunno    -- ^ Possibly terminates
  deriving (Eq, Show)

instance JoinSemiLattice Termination where
  Diverges \/ t = t
  t \/ Diverges = t
  _ \/ _ = Dunno

instance BoundedJoinSemiLattice Termination where
  bottom = Diverges

instance MeetSemiLattice Termination where
  Diverges /\ _ = Diverges
  _ /\ Diverges = Diverges
  _ /\ _ = Dunno

instance BoundedMeetSemiLattice Termination where
  top = Dunno

-- | In the case of divergence, we want to assume
-- an optimistic 'HyperStrict' strictness for any variables
-- not present in the 'StrEnv'.
-- Otherwise, those variables are possible absent and thus used
-- lazily.
defaultStr :: Termination -> Strictness
defaultStr Dunno    = Lazy
defaultStr Diverges = HyperStrict

-- | Tracks strictness on free variables of a possibly diverging
-- expression.
data StrEnv
  = StrEnv !(VarEnv Strictness) !Termination
  deriving (Eq, Show)

instance JoinSemiLattice StrEnv where
  (StrEnv a t1) \/ (StrEnv b t2) =
    StrEnv (plusVarEnv_CD (\/) a (defaultStr t1) b (defaultStr t2)) (t1 \/ t2)

instance BoundedJoinSemiLattice StrEnv where
  bottom = StrEnv emptyVarEnv Diverges

instance MeetSemiLattice StrEnv where
  (StrEnv a t1) /\ (StrEnv b t2) =
    StrEnv (plusVarEnv_CD (/\) a (defaultStr t1) b (defaultStr t2)) (t1 /\ t2)

instance BoundedMeetSemiLattice StrEnv where
  top = StrEnv emptyVarEnv Dunno

unitStrEnv :: Var -> Strictness -> StrEnv
unitStrEnv id_ str = StrEnv (unitVarEnv id_ str) Dunno

peelStrEnv :: Id -> StrEnv -> (Strictness, StrEnv)
peelStrEnv id_ (StrEnv env t) =
  (fromMaybe (defaultStr t) (lookupVarEnv env id_), StrEnv (delVarEnv env id_) t)

peelFV :: Id -> StrType -> (Strictness, StrType)
peelFV id_ ty =
  let (str, fvs') = peelStrEnv id_ (fvs ty)
  in (str, ty { fvs = fvs' })

lazifyStrEnv :: StrEnv -> StrEnv
lazifyStrEnv _ = top

data ArgStr
  = BottomArgStr
  | TopArgStr
  | ConsArgStr !Strictness !ArgStr
  deriving Eq

instance Show ArgStr where
  show argStr = "[" ++ impl argStr ++ "]"
    where
      impl BottomArgStr           = "B,B.."
      impl TopArgStr              = "L,L.."
      impl (ConsArgStr str args') = show str ++ "," ++ impl args'

instance JoinSemiLattice ArgStr where
  BottomArgStr \/ s = s
  s \/ BottomArgStr = s
  TopArgStr \/ _ = TopArgStr
  _ \/ TopArgStr = TopArgStr
  (ConsArgStr s1 a1) \/ (ConsArgStr s2 a2) = ConsArgStr (s1 \/ s2) (a1 \/ a2)

instance BoundedJoinSemiLattice ArgStr where
  bottom = BottomArgStr

-- | This instance doesn't make a lot of sense semantically,
-- but it's the dual to the 'JoinSemiLattice' instance.
-- We mostly need this for 'top'.
instance MeetSemiLattice ArgStr where
  BottomArgStr /\ _ = BottomArgStr
  _ /\ BottomArgStr = BottomArgStr
  TopArgStr /\ s = s
  s /\ TopArgStr = s
  (ConsArgStr s1 a1) /\ (ConsArgStr s2 a2) = ConsArgStr (s1 /\ s2) (a1 /\ a2)

instance BoundedMeetSemiLattice ArgStr where
  top = TopArgStr

consArgStr :: Strictness -> ArgStr -> ArgStr
consArgStr Lazy TopArgStr           = TopArgStr
consArgStr HyperStrict BottomArgStr = BottomArgStr
consArgStr s a                      = ConsArgStr s a

unconsArgStr :: ArgStr -> (Strictness, ArgStr)
unconsArgStr BottomArgStr     = (bottom, BottomArgStr)
unconsArgStr TopArgStr        = (top, TopArgStr)
unconsArgStr (ConsArgStr s a) = (s, a)

data StrType
  = StrType
  { fvs  :: !StrEnv
  , args :: !ArgStr
  } deriving (Eq, Show)

instance JoinSemiLattice StrType where
  (StrType fvs1 args1) \/ (StrType fvs2 args2) =
    StrType (fvs1 \/ fvs2) (args1 \/ args2)

instance BoundedJoinSemiLattice StrType where
  bottom = StrType bottom bottom

-- | This instance doesn't make a lot of sense semantically,
-- but it's the dual to the 'JoinSemiLattice' instance.
-- We mostly need this for 'top'.
instance MeetSemiLattice StrType where
  (StrType fvs1 args1) /\ (StrType fvs2 args2) =
    StrType (fvs1 /\ fvs2) (args1 /\ args2)

instance BoundedMeetSemiLattice StrType where
  top = StrType top top

overFVs :: (StrEnv -> (a, StrEnv)) -> StrType -> (a, StrType)
overFVs f ty =
  let (a, fvs') = f (fvs ty)
  in (a, ty { fvs = fvs' })

overArgs :: (ArgStr -> (a, ArgStr)) -> StrType -> (a, StrType)
overArgs f ty =
  let (a, args') = f (args ty)
  in (a, ty { args = args' })

modifyArgs :: (ArgStr -> ArgStr) -> StrType -> StrType
modifyArgs f = snd . overArgs (\a -> ((), f a))

emptyStrType :: StrType
emptyStrType = top

unitStrType :: Id -> Strictness -> StrType
unitStrType id_ str = StrType (unitStrEnv id_ str) top

-- | Sequential composition, or Par or both.
-- This is right biased, meaning that it will return the
-- argument strictness of the right argument.
bothStrType :: StrType -> StrType -> StrType
bothStrType (StrType fvs1 _) (StrType fvs2 args2) =
  StrType (fvs1 /\ fvs2) args2

lazifyStrType :: StrType -> StrType
lazifyStrType ty = StrType fvs' (args ty)
  -- Doesn't change argument strictness, but
  -- it shouldn't actually matter.
  -- Anyway, ArgStr always corresponds to a
  -- single incoming call.
  where
    fvs' = lazifyStrEnv (fvs ty)

-- | Tracks annotations in the syntax tree.
-- Has an instance of 'JoinSemiLattice', but
-- really doesn't allow overwriting annotations.
newtype Annotations
  = Ann (VarEnv Strictness)
  deriving (Eq, Show)

emptyAnnotations :: Annotations
emptyAnnotations = Ann emptyVarEnv

instance JoinSemiLattice Annotations where
  (Ann a) \/ (Ann b) = Ann $ plusVarEnv_C (\/) a b

instance BoundedJoinSemiLattice Annotations where
  bottom = emptyAnnotations

overwriteError :: (Show a, Show b) => a -> b -> c
overwriteError old new =
  error $
    "Should never overwrite an annotation. Old: "
    ++ show old ++ ", New: "
    ++ show new

annotate :: Id -> Strictness -> Annotations -> Annotations
annotate id_ str (Ann anns) = Ann (extendVarEnv_C overwriteError anns id_ str)

lookupAnnotation :: Id -> Annotations -> Maybe Strictness
lookupAnnotation id_ (Ann env) = lookupVarEnv env id_

newtype StrLattice
  = StrLattice (StrType, Annotations)
  deriving (Eq, Show, JoinSemiLattice, BoundedJoinSemiLattice)

mkStrLattice :: StrType -> Annotations -> StrLattice
mkStrLattice ty ann = StrLattice (ty, ann)

emptyStrLattice :: StrLattice
emptyStrLattice = mkStrLattice emptyStrType emptyAnnotations

strType :: StrLattice -> StrType
strType (StrLattice (ty, _)) = ty

annotations :: StrLattice -> Annotations
annotations (StrLattice (_, anns)) = anns

peelAndAnnotateFV :: Id -> StrLattice -> StrLattice
peelAndAnnotateFV id_ (StrLattice (ty, anns)) =
  let (str, ty') = peelFV id_ ty
      anns' = annotate id_ str anns
  in mkStrLattice ty' anns'

peelAndAnnotateFVs :: [Id] -> StrLattice -> StrLattice
peelAndAnnotateFVs ids latt = foldr peelAndAnnotateFV latt ids