feldspar-language-0.7: src/Feldspar/Core/Constructs/Binding.hs
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
--
-- Copyright (c) 2009-2011, ERICSSON AB
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- * Redistributions of source code must retain the above copyright notice,
-- this list of conditions and the following disclaimer.
-- * Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
-- * Neither the name of the ERICSSON AB nor the names of its contributors
-- may be used to endorse or promote products derived from this software
-- without specific prior written permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--
-- | Interpretation of binding constructs
module Feldspar.Core.Constructs.Binding
( module Language.Syntactic.Constructs.Binding
, optimizeLambda
, optimizeFunction
-- , optimizeFunctionFix
, subst
, betaReduce
, prjLambda
, cLambda
, reuseCLambda
, collectLetBinders
) where
import Control.Monad.Reader
import Data.Maybe
import Data.Map
import Data.Typeable (Typeable, gcast)
--import Data.Lens.Common
import Language.Syntactic
import Language.Syntactic.Constructs.Binding hiding (subst,betaReduce)
import Language.Syntactic.Constructs.Binding.HigherOrder (CLambda)
import Feldspar.Lattice
import Feldspar.Core.Types
import Feldspar.Core.Interpretation
instance Sharable Variable -- `codeMotion` will not share variables anyway
instance Sharable Lambda -- Will not be shared anyway because we disallow variables of `->` type
instance Sharable Let
instance Monotonic Variable
instance Monotonic Lambda
instance Monotonic Let
subst :: forall dom a b
. ( Constrained dom
, CLambda Type :<: dom
, (Variable :|| Type) :<: dom
)
=> VarId -- ^ Variable to be substituted
-> ASTF (dom :|| Typeable) a -- ^ Expression to substitute for
-> ASTF (dom :|| Typeable) b -- ^ Expression to substitute in
-> ASTF (dom :|| Typeable) b
subst v new = go
where
go :: AST (dom :|| Typeable) c -> AST (dom :|| Typeable) c
go a@((prjLambda -> Just (SubConstr2 (Lambda w))) :$ _)
| v==w = a -- Capture
go (f :$ a) = go f :$ go a
go var
| Just (C' (Variable w)) <- prjF var
, v==w
, Dict <- exprDictSub pTypeable new
, Dict <- exprDictSub pTypeable var
, Just new' <- gcast new
= new'
go a = a
-- TODO Should be possible to use the one in Syntactic instead
betaReduce
:: ( Constrained dom
, CLambda Type :<: dom
, (Variable :|| Type) :<: dom
)
=> ASTF (dom :|| Typeable) a -- ^ Argument
-> ASTF (dom :|| Typeable) (a -> b) -- ^ Function to be reduced
-> ASTF (dom :|| Typeable) b
betaReduce new (lam :$ body)
| Just (SubConstr2 (Lambda v)) <- prjLambda lam = subst v new body
-- TODO Should be possible to use the one in Syntactic instead
optimizeLambda :: ( CLambda Type :<: dom
, OptimizeSuper dom)
=> FeldOpts
-> (ASTF (dom :|| Typeable) b -> Opt (ASTF (Decor Info (dom :|| Typeable)) b)) -- ^ Optimization of the body
-> Info a
-> CLambda Type (b :-> Full (a -> b))
-> Args (AST (dom :|| Typeable)) (b :-> Full (a -> b))
-> Opt (ASTF (Decor Info (dom :|| Typeable)) (a -> b))
optimizeLambda opts opt info lam@(SubConstr2 (Lambda v)) (body :* Nil)
| Dict <- exprDict body
= do
body' <- localVar v info $ opt body
constructFeatUnOpt opts lam (body' :* Nil)
-- | Assumes that the expression is a 'Lambda'
optimizeFunction :: ( (Variable :|| Type) :<: dom
, CLambda Type :<: dom
, Let :<: dom
, OptimizeSuper dom
)
=> FeldOpts
-> (ASTF (dom :|| Typeable) b -> Opt (ASTF (Decor Info (dom :|| Typeable)) b)) -- ^ Optimization of the body
-> Info a
-> (ASTF (dom :|| Typeable) (a -> b) -> Opt (ASTF (Decor Info (dom :|| Typeable)) (a -> b)))
optimizeFunction opts opt info e
| e'@(bs, _) <- collectLetBinders e
, not (Prelude.null bs)
= optimizeLet opts opt info e'
optimizeFunction opts opt info a@(sym :$ body)
| Dict <- exprDict a
, Dict <- exprDict body
, Just (lam@(SubConstr2 (Lambda _))) <- prjLambda sym
= optimizeLambda opts opt info lam (body :* Nil)
optimizeFunction _ _ info a
= error $ "optimizeFunction: AST is not a function: " ++ show a ++ "\n" ++ show (infoType info)
optimizeLet
:: ( (Variable :|| Type) :<: dom
, CLambda Type :<: dom
, Let :<: dom
, OptimizeSuper dom
)
=> FeldOpts
-> (ASTF (dom :|| Typeable) b -> Opt (ASTF (Decor Info (dom :|| Typeable)) b)) -- ^ Optimization of the body
-> Info a
-> ([(VarId, ASTB (dom :|| Typeable) Type)], ASTF (dom :|| Typeable) (a -> b))
-> Opt (ASTF (Decor Info (dom :|| Typeable)) (a -> b))
optimizeLet opts opt info ((v, ASTB e):t, bd)
| Dict <- exprDict bd
, Dict <- exprDict e
= do
e' <- optimizeM opts e
bd' <- localVar v (getInfo e') $ optimizeLet opts opt info (t, bd)
bd'' <- constructFeatUnOpt opts (cLambda v) (bd' :* Nil)
constructFeatUnOpt opts Let (e' :* bd'' :* Nil)
optimizeLet opts opt info ([], e) = optimizeFunction opts opt info e
{-
optimizeFunBody :: (Lambda TypeCtx :<: dom, Optimize dom dom, Typeable a)
=> (ASTF dom a -> Opt (ASTF (Decor Info dom) a)) -- ^ Optimization of the body
-> Env -- ^ Environment (instead of using 'Opt')
-> VarId -- ^ Bound variable
-> ASTF dom a -- ^ Body
-> Info a -- ^ 'Info' of bound variable
-> ASTF (Decor Info dom) a
optimizeFunBody opt env v body info =
flip runReader env $ localVar v info $ opt body
-- | Assumes that the expression is a 'Lambda'
optimizeFunctionFix
:: forall dom a
. (Lambda TypeCtx :<: dom, Optimize dom dom, Type a)
=> (ASTF dom a -> Opt (ASTF (Decor Info dom) a)) -- ^ Optimization of the body
-> Info a
-> (ASTF dom (a -> a) -> Opt (ASTF (Decor Info dom) (a -> a)))
optimizeFunctionFix opt info (lam :$ body)
| Just (Lambda v) <- prjCtx typeCtx lam
= do
env <- ask
let aLens :: Lens (Info a) (Size a)
aLens = lens infoSize (\sz inf -> inf {infoSize = sz})
let bLens :: Lens (ASTF (Decor Info dom) a) (Size a)
bLens = lens (infoSize . getInfo)
(\sz a -> updateDecor (\inf -> inf {infoSize = sz}) a)
let body' = fst $ boundedLensedFixedPoint 1 aLens bLens
(optimizeFunBody opt env v body)
info
-- Using 1 as bound is motivated by the fact that a higher number
-- leads to exponential blowup when there are many nested
-- iterations. Since it is probably uncommon to have very deeply
-- nested loops, it might be fine to increase the bound. However
-- it is not clear that we gain anything by doing so, other than
-- in very special cases.
constructFeatUnOpt (Lambda v `withContext` typeCtx) (body' :* Nil)
-}
instance ( (Variable :|| Type) :<: dom
, OptimizeSuper dom)
=> Optimize (Variable :|| Type) dom
where
constructFeatUnOpt _ (C' (Variable v)) Nil
= reader $ \env -> case Prelude.lookup v (varEnv env) of
Nothing -> error $
"optimizeFeat: can't get size of free variable: v" ++ show v
Just (SomeInfo info) ->
let info' = (fromJust $ gcast info) {infoVars = singleton v (SomeType $ infoType info) }
in Sym $ Decor info' $ C' $ inj $ c' (Variable v)
instance ( CLambda Type :<: dom
, OptimizeSuper dom)
=> Optimize (CLambda Type) dom
where
-- | Assigns a 'universal' size to the bound variable. This only makes sense
-- for top-level lambdas. For other uses, use 'optimizeLambda' instead.
optimizeFeat opts lam@(SubConstr2 (Lambda _))
| Dict <- exprDict lam
= optimizeLambda opts (optimizeM opts) (mkInfo universal) lam
constructFeatUnOpt _ lam@(SubConstr2 (Lambda v)) (body :* Nil)
| Dict <- exprDict lam
, Info t sz vars _ <- getInfo body
= do
src <- asks sourceEnv
let info = Info (FunType typeRep t) (universal, sz) (delete v vars) src
return $ (Sym $ Decor info $ C' $ inj lam) :$ body
instance SizeProp Let
where
sizeProp Let (_ :* WrapFull f :* Nil) = snd $ infoSize f
instance
( Let :<: dom
, (Variable :|| Type) :<: dom
, CLambda Type :<: dom
, OptimizeSuper dom
) =>
Optimize Let dom
where
optimizeFeat opts lt@Let (a :* f :* Nil) = do
a' <- optimizeM opts a
f' <- optimizeFunction opts (optimizeM opts) (getInfo a') f
constructFeat opts lt (a' :* f' :* Nil)
constructFeatOpt _ Let (a :* (lam :$ var) :* Nil)
| Just (C' (Variable v2)) <- prjF var
, Just (SubConstr2 (Lambda v1)) <- prjLambda lam
, v1 == v2
= return $ fromJust $ gcast a
constructFeatOpt opts Let (var :* f :* Nil)
| Just (C' (Variable _)) <- prjF var
= optimizeM opts $ betaReduce (stripDecor var) (stripDecor f)
-- (letBind (letBind e1 (\x -> e2)) (\y -> e3) ==>
-- letBind e1 (\x -> letBind e2 (\y-> e3))
--
-- Test case:
--
-- stestL2 :: Data Index -> Data Length -> Data [[Index]]
-- stestL2 m x = parallel x (\x1 -> let z = let y = x `mod` m in (y, y) in parallel 2 (\x -> fst z))
constructFeatOpt opts lt1@Let ((lt2 :$ x :$ (lam :$ bd)) :* y :* Nil)
| Just Let <- prj lt2
, Just lam'@(SubConstr2 (Lambda _)) <- prjLambda lam
, SICS `inTarget` opts
= do
bb <- constructFeat opts lt1 (bd :* y :* Nil)
bd' <- constructFeat opts (reuseCLambda lam') (bb :* Nil)
constructFeatUnOpt opts Let (x :* bd' :* Nil)
-- Hoist let-bound constants upwards.
--
-- (letBind e1 (\x -> letBind v (\y -> e2)) ==>
-- letBind v (\y -> letBind e1 (\x-> e2))
constructFeatOpt opts lt1@Let (e :* (lam1 :$ (lt2 :$ v :$ (lam2 :$ bd))) :* Nil)
| Just Let <- prj lt2
, Nothing <- viewLiteral e
, Just _ <- viewLiteral v
, Just lam1'@(SubConstr2 (Lambda{})) <- prjLambda lam1
, Just lam2'@(SubConstr2 (Lambda{})) <- prjLambda lam2
, SICS `inTarget` opts
= do
bb <- constructFeat opts (reuseCLambda lam1') (bd :* Nil)
bb' <- constructFeat opts lt1 (e :* bb :* Nil)
bd' <- constructFeat opts (reuseCLambda lam2') (bb' :* Nil)
constructFeatUnOpt opts Let (v :* bd' :* Nil)
constructFeatOpt opts a args = constructFeatUnOpt opts a args
constructFeatUnOpt opts Let args@(_ :* (lam :$ body) :* Nil)
| Just (SubConstr2 (Lambda _)) <- prjLambda lam
, Info {infoType = t} <- getInfo body
= constructFeatUnOptDefaultTyp opts t Let args
prjLambda :: (Project (CLambda Type) dom)
=> dom sig -> Maybe (CLambda Type sig)
prjLambda = prj
cLambda :: Type a => VarId -> CLambda Type (b :-> Full (a -> b))
cLambda = SubConstr2 . Lambda
-- | Allow an existing binding to be used with a body of a different type
reuseCLambda :: CLambda Type (b :-> Full (a -> b)) -> CLambda Type (c :-> Full (a -> c))
reuseCLambda (SubConstr2 (Lambda v)) = SubConstr2 (Lambda v)
-- | Collects the immediate let bindings in a list and returns the first non-let expression
--
-- This function can be useful when let bindings get in the way of pattern matching on a
-- sub-expressions.
collectLetBinders :: forall dom a .
( Project Let dom
, Project (CLambda Type) dom
, ConstrainedBy dom Typeable
) => ASTF dom a ->
( [(VarId, ASTB dom Type)]
, ASTF dom a
)
collectLetBinders = go []
where
go
:: [(VarId, ASTB dom Type)]
-> ASTF dom a
-> ( [(VarId, ASTB dom Type)]
, ASTF dom a
)
go bs (lt :$ e :$ (lam :$ body))
| Just (SubConstr2 (Lambda v)) <- prjLambda lam
, Just Let <- prj lt
, Dict <- exprDict e
= go ((v, ASTB e):bs) body
go bs e = (reverse bs, e)