syntactic-0.7: Language/Syntactic/Sharing/SimpleCodeMotion.hs
-- | Simple code motion transformation performing common sub-expression
-- elimination and variable hoisting. Note that the implementation is very
-- inefficient.
--
-- The code is based on an implementation by Gergely Dévai.
module Language.Syntactic.Sharing.SimpleCodeMotion
( codeMotion
, reifySmart
) where
import Control.Monad.State
import Data.Set as Set
import Data.Typeable
import Data.Proxy
import Language.Syntactic
import Language.Syntactic.Constructs.Binding
import Language.Syntactic.Constructs.Binding.HigherOrder
-- | Substituting a sub-expression
substitute :: forall ctx dom a b
. ( Typeable a
, Typeable b
, Variable ctx :<: dom
, Lambda ctx :<: dom
, ExprEq dom
)
=> Proxy ctx
-> ASTF dom a -- ^ Sub-expression to be replaced
-> ASTF dom a -- ^ Replacing sub-expression
-> ASTF dom b -- ^ Whole expression
-> ASTF dom b
substitute ctx x y a = subst a
where
subst :: Typeable c => AST dom c -> AST dom c
subst a | Just y' <- gcast y, alphaEq ctx x a = y'
subst (f :$: a) = subst f :$: subst a
subst a = a
-- | Count the number of occurrences of a sub-expression
count :: (Variable ctx :<: dom, Lambda ctx :<: dom, ExprEq dom)
=> Proxy ctx
-> AST dom a -- ^ Expression to count
-> AST dom b -- ^ Expression to count in
-> VarId
count ctx a b
| alphaEq ctx a b = 1
count ctx a (f :$: b) = count ctx a f + count ctx a b
count ctx a _ = 0
nonTerminal :: AST dom a -> Bool
nonTerminal (_ :$: _) = True
nonTerminal _ = False
data SomeAST ctx dom
where
SomeAST :: (Sat ctx a, Typeable a) => ASTF dom a -> SomeAST ctx dom
-- | Environment for the expression in the 'choose' function
data Env ctx dom = Env
{ context :: Proxy ctx
, inLambda :: Bool -- ^ Whether the current expression is inside a lambda
, counter :: SomeAST ctx dom -> VarId
-- ^ Counting the number of occurrences of an expression in the
-- environment
, dependencies :: Set VarId
-- ^ The set of variables that are not allowed to occur in the chosen
-- expression
}
independent :: (Variable ctx :<: dom) => Env ctx dom -> AST dom a -> Bool
independent env (prjCtx (context env) -> Just (Variable v)) =
not (v `member` dependencies env)
independent env (f :$: a) = independent env f && independent env a
independent _ _ = True
-- | Checks whether a sub-expression in a given environment can be lifted out
liftable :: (Variable ctx :<: dom, Lambda ctx :<: dom, Sat ctx a, Typeable a) =>
Env ctx dom -> ASTF dom a -> Bool
liftable env a = independent env a && heuristic
-- Lifting dependent expressions is semantically incorrect
where
heuristic = nonTerminal a && (inLambda env || (counter env (SomeAST a) > 1))
-- | Choose a sub-expression to share
choose :: forall ctx dom a
. ( Variable ctx :<: dom
, Lambda ctx :<: dom
, ExprEq dom
, MaybeWitnessSat ctx dom
, Typeable a
)
=> ASTF dom a -> Maybe (SomeAST ctx dom)
choose a = chooseEnv env a
where
ctx = Proxy :: Proxy ctx
env :: Env ctx dom
env = Env
{ inLambda = False
, counter = \(SomeAST b) -> count ctx b a
, dependencies = empty
, context = ctx
}
-- | Choose a sub-expression to share in an 'Env' environment
chooseEnv
:: ( Variable ctx :<: dom
, Lambda ctx :<: dom
, MaybeWitnessSat ctx dom
, Typeable a
)
=> Env ctx dom -> ASTF dom a -> Maybe (SomeAST ctx dom)
chooseEnv env a
| Just SatWit <- maybeWitnessSat (context env) a
, liftable env a
= Just (SomeAST a)
| otherwise = chooseEnvSub env a
-- | Like 'chooseEnv', but does not consider the top expression for sharing
chooseEnvSub
:: (Variable ctx :<: dom, Lambda ctx :<: dom, MaybeWitnessSat ctx dom)
=> Env ctx dom -> AST dom a -> Maybe (SomeAST ctx dom)
chooseEnvSub env ((prjCtx (context env) -> Just (Lambda v)) :$: a) =
chooseEnv env' a
where
env' = env
{ inLambda = True
, dependencies = insert v (dependencies env)
}
chooseEnvSub env (f :$: a) = chooseEnvSub env f `mplus` chooseEnv env a
chooseEnvSub _ _ = Nothing
-- | Perform common sub-expression elimination and variable hoisting
codeMotion :: forall ctx dom a
. ( Variable ctx :<: dom
, Lambda ctx :<: dom
, Let ctx ctx :<: dom
, ExprEq dom
, MaybeWitnessSat ctx dom
, Typeable a
)
=> Proxy ctx -> ASTF dom a -> State VarId (ASTF dom a)
codeMotion ctx a
| Just SatWit <- maybeWitnessSat ctx a
, Just b <- choose a
= share b
| otherwise = descend ctx a
where
share :: Sat ctx a => SomeAST ctx dom -> State VarId (ASTF dom a)
share (SomeAST b) = do
b' <- codeMotion ctx b
v <- get; put (v+1)
let x = inject (Variable v `withContext` ctx)
body <- codeMotion ctx $ substitute ctx b x a
return
$ inject (letBind ctx)
:$: b'
:$: (inject (Lambda v `withContext` ctx) :$: body)
descend
:: ( Variable ctx :<: dom
, Lambda ctx :<: dom
, Let ctx ctx :<: dom
, ExprEq dom
, MaybeWitnessSat ctx dom
)
=> Proxy ctx -> AST dom a -> State VarId (AST dom a)
descend ctx (f :$: a) = liftM2 (:$:) (descend ctx f) (codeMotion ctx a)
descend _ a = return a
-- | Like 'reify' but with common sub-expression elimination and variable
-- hoisting
reifySmart
:: ( Let ctx ctx :<: dom
, ExprEq dom
, MaybeWitnessSat ctx dom
, Syntactic a (HODomain ctx dom)
)
=> Proxy ctx
-> a
-> ASTF (Lambda ctx :+: Variable ctx :+: dom) (Internal a)
reifySmart ctx = flip evalState 0 . (codeMotion ctx <=< reifyM) . desugar