CaseCopyPropagation optimising transformation.

This commit is contained in:
Andor Penzes 2018-03-11 22:51:05 +01:00
parent 964eb80ebb
commit 4ae27c2a4c
3 changed files with 358 additions and 0 deletions

View File

@ -39,6 +39,8 @@ instance FromTag CPat where
tag name a = TagPat $ Tag C name
(@:) cname params = NodePat (Tag C cname) params
(@@:) fname params = NodePat (Tag F fname) params
instance FromTag Val where
tag name a = ValTag $ Tag C name
(@:) cname params = ConstTagNode (Tag C cname) (Var <$> params)

View File

@ -0,0 +1,162 @@
{-# LANGUAGE LambdaCase, EmptyCase, ViewPatterns #-}
module Transformations.Optimising.CaseCopyPropagation where
import Control.Arrow
import Grin
import Data.Functor.Foldable
import Control.Monad.Free
import Control.Comonad.Cofree
import Data.Monoid hiding (Alt)
import Debug.Trace
import Data.Maybe
import Data.List (find)
{-
When implementing the transformation we need to, for each case expression:
1. Examine the return sites of all the alternatives
2. Decide if the transformation applies (all returns must be similar units)
3. Possible transform all the return sites
4. Continue transforming the code in each alternative
The transformation can eve be done in a single pass and linear time,
using techniques for circular algorithms.
[Bir84, Joh87a]
Bir 84: R S Bird. Using Circular Programs to Eliminate Multiple Traversals of Data. 1984
Joh87a: T. Johnson. Attribute Grammars as a Functional Programming Paradigm. 1987
-}
-- * Collection
type Step = ExpF ()
type Path = [Step]
data Info = Info
{ returns :: [(Path, Maybe Tag)]
, cases :: [(Path, Tag)]
}
deriving Show
instance Monoid Info where
mempty = Info [] []
mappend (Info r1 c1) (Info r2 c2) = Info (r1 <> r2) (c1 <> c2)
toStep :: ExpF a -> Step
toStep = fmap (const ())
isEmptyInfo :: Info -> Bool
isEmptyInfo (Info returns cases) = null returns && null cases
addStep :: Step -> Info -> Info
addStep s (Info returns cases) = Info
(map (\(p, v) -> ((s:p), v)) returns)
(map (\(p, v) -> ((s:p), v)) cases)
stepInside :: Step -> Info -> Info
stepInside s (Info returns cases) = Info
(map (first tail) $ filter (([s] ==) . take 1 . fst) returns)
(map (first tail) $ filter (([s] ==) . take 1 . fst) cases)
zoom :: Int -> Info -> Info
zoom n (Info returns cases) = Info (map (first (drop n)) returns) (map (first (drop n)) cases)
caseTagOnStep :: Step -> Info -> Maybe Tag
caseTagOnStep s (Info returns cases) = fmap snd $ find (([s] ==) . fst) $ cases
caseFocusOnStep :: Step -> Info -> Bool
caseFocusOnStep s i = isJust $ caseTagOnStep s i
collectInfo :: Exp -> Info
collectInfo = para convert where
convert :: ExpF (Exp, Info) -> Info
convert e = addStep (toStep e) $ case e of
EBindF (SBlock _, se) _ (_, r) -> se <> r
EBindF _ _ (EBind _ _ _, r) -> r
EBindF _ _ (ECase _ _, r) -> r
EBindF _ _ (SReturn (ConstTagNode tag@(Tag _ _) [v]), _) -> mempty { returns = [(mempty, Just tag)] } -- Good node
EBindF _ _ (SReturn _, _) -> mempty { returns = [(mempty, Nothing)] } -- Bad node
ProgramF (mconcat . map snd -> info) -> info
DefF _ _ (_, info) -> info { returns = mempty }
ECaseF v ((mconcat . map snd) -> info) -> case info of
Info returns cases -> case (allTheSame $ map snd returns) of
Just (Just tag) -> Info mempty (([], tag):cases) -- The case is the same return values
Nothing -> Info mempty cases
AltF pat (ei, info) -> info
SBlockF (ei, info) -> info
_ -> mempty
-- * Build
data BuilderState
= Build Exp Info Bool
| Skip Int Exp Info Bool
caseCopyPropagation :: Exp -> Exp
caseCopyPropagation e = apo builder (Build e info False) where
info = collectInfo e
builder :: BuilderState -> ExpF (Either Exp BuilderState)
-- Skip some expressions without transforming them.
builder (Skip 0 e0 i r) = builder (Build e0 i r)
builder (Skip n e0 i r) = fmap (Right . (\e1 -> (Skip (n-1) e1 i r))) $ project e0
-- Real transformation.
builder (Build e i False) | isEmptyInfo i = fmap Left $ project e
builder (Build e i rewrite) = case e of
-- Just step in in the followings
Program defs -> ProgramF (map stepIn defs)
Def n args body -> DefF n args (stepIn body)
SBlock body -> SBlockF $ stepIn body
Alt cpat body -> AltF cpat (stepIn body)
-- Exp: Insert nodes, skips them in the recursion and continue the transformation on the Case node.
EBind lhs@(SBlock cs@(ECase var@(Var n) alts)) pat rhs
| caseFocusOnStep (toStepE cs) i1 ->
EBindF
(Right $ Skip 3
(SBlock (EBind lhs newVar (SReturn (ConstTagNode (fromJust (caseTagOnStep (toStepE cs) i1)) [newVar]))))
i1
True)
pat
(stepIn rhs)
| otherwise -> EBindF (stepInF lhs) pat (stepIn rhs)
where
i1 = zoom 2 i
newVar = Var (n <> "'")
EBind lhs pat rhs | isSimpleExp lhs -> EBindF (Left lhs) pat (stepIn rhs)
EBind lhs pat rhs -> EBindF (stepIn lhs) pat (stepIn rhs)
ECase val alts
| caseFocusOnStep step i -> ECaseF val (map stepInT alts)
| otherwise -> ECaseF val (map stepIn alts)
r@(SReturn (ConstTagNode (Tag _ _) [v]))
| rewrite -> SReturnF v
| otherwise -> Left <$> project r
-- Simple Exp
rest -> Left <$> project rest
where
stepIn e0 = Right (Build e0 (stepInside step i) rewrite)
stepInT e0 = Right (Build e0 (stepInside step i) True)
stepInF e0 = Right (Build e0 (stepInside step i) False)
step = toStepE e
toStepE :: Exp -> Step
toStepE = toStep . project
-- * Utils
allTheSame :: (Eq a) => [a] -> Maybe a
allTheSame [] = Nothing
allTheSame (x:xs) = if any (/=x) xs then Nothing else Just x

View File

@ -0,0 +1,194 @@
{-# LANGUAGE TypeApplications, OverloadedStrings #-}
module Transformations.Optimising.CaseCopyPropagationSpec where
import Transformations.Optimising.CaseCopyPropagation
import Test.Hspec
import Free
import Grin
import Test
import Assertions
spec :: Spec
spec = do
it "Example from Figure 4.26" $ do
before <- buildExpM $
"m0" <=: store @Int 3 $
"u" <=: block (switch "v"
[ ("foo" @@: ["a"], "y'" <=: app "foo" ["a"] $
unit @Val ("Int" @: ["y'"]))
, ("bar" @@: ["b"], "z'" <=: app "bar" ["b"] $
unit @Val ("Int" @: ["z'"]))
, ("Int" @: ["x'"], unit @Val ("Int" @: ["x'"]))
]) $
unit @Var "m0"
after <- buildExpM $
"m0" <=: store @Int 3 $
"u" <=: (block $
"v'" <=: (block $ switch "v"
[ ("foo" @@: ["a"], "y'" <=: app "foo" ["a"] $
unit @Var "y'")
, ("bar" @@: ["b"], "z'" <=: app "bar" ["b"] $
unit @Var "z'")
, ("Int" @: ["x'"], unit @Var "x'")
]) $
unit @Val ("Int" @: ["v'"])) $
unit @Var "m0"
caseCopyPropagation before `sameAs` after
it "One node has no Int tagged value" $ do
before <- buildExpM $
"m0" <=: store @Int 3 $
"u" <=: block (switch "v"
[ ("foo" @@: ["a"], "y'" <=: app "foo" ["a"] $
unit @Val ("Int" @: ["y'"]))
, ("bar" @@: ["b"], "z'" <=: app "bar" ["b"] $
unit @Val ("Float" @: ["z'"]))
, ("Int" @: ["x'"], unit @Val ("Int" @: ["x'"]))
]) $
unit @Var "m0"
after <- buildExpM $
"m0" <=: store @Int 3 $
"u" <=: block (switch "v"
[ ("foo" @@: ["a"], "y'" <=: app "foo" ["a"] $
unit @Val ("Int" @: ["y'"]))
, ("bar" @@: ["b"], "z'" <=: app "bar" ["b"] $
unit @Val ("Float" @: ["z'"]))
, ("Int" @: ["x'"], unit @Val ("Int" @: ["x'"]))
]) $
unit @Var "m0"
caseCopyPropagation before `sameAs` after
it "Embedded good case" $ do
before <- buildExpM $
"m0" <=: store @Int 3 $
"u" <=: block (switch "v"
[ ("foo" @@: ["a"], "y'" <=: app "foo" ["a"] $
unit @Val ("Int" @: ["y'"]))
, ("bar" @@: ["b"], "z'" <=: app "bar" ["b"] $
unit @Val ("Int" @: ["z'"]))
, ("Int" @: ["x'"], "u1" <=: block (switch "v1"
[ ("foo" @@: ["a1"], "y1'" <=: app "foo" ["a1"] $
unit @Val ("Int" @: ["y1'"]))
, ("bar" @@: ["b1"], "z1'" <=: app "bar" ["b1"] $
unit @Val ("Int" @: ["z1'"]))
, ("Int" @: ["x1'"], unit @Val ("Int" @: ["x1'"]))
]) $
unit @Val ("Int" @: ["x'"]))
]) $
unit @Var "m0"
after <- buildExpM $
"m0" <=: store @Int 3 $
"u" <=: (block $
"v'" <=: (block $ switch "v"
[ ("foo" @@: ["a"], "y'" <=: app "foo" ["a"] $
unit @Var "y'")
, ("bar" @@: ["b"], "z'" <=: app "bar" ["b"] $
unit @Var "z'")
, ("Int" @: ["x'"], "u1" <=: (block $
"v1'" <=: (block $ switch "v1"
[ ("foo" @@: ["a1"], "y1'" <=: app "foo" ["a1"] $
unit @Var "y1'")
, ("bar" @@: ["b1"], "z1'" <=: app "bar" ["b1"] $
unit @Var "z1'")
, ("Int" @: ["x1'"], unit @Var "x1'")
]) $
unit @Val ("Int" @: ["v1'"])) $
unit @Var "x'")
]) $
unit @Val ("Int" @: ["v'"])) $
unit @Var "m0"
caseCopyPropagation before `sameAs` after
it "Embedded bad case" $ do
before <- buildExpM $
"m0" <=: store @Int 3 $
"u" <=: block (switch "v"
[ ("foo" @@: ["a"], "y'" <=: app "foo" ["a"] $
unit @Val ("Int" @: ["y'"]))
, ("bar" @@: ["b"], "z'" <=: app "bar" ["b"] $
unit @Val ("Int" @: ["z'"]))
, ("Int" @: ["x'"], "u1" <=: block (switch "v1"
[ ("foo" @@: ["a1"], "y1'" <=: app "foo" ["a1"] $
unit @Val ("Int" @: ["y1'"]))
, ("bar" @@: ["b1"], "z1'" <=: app "bar" ["b1"] $
unit @Val ("Float" @: ["z1'"]))
, ("Int" @: ["x1'"], unit @Val ("Int" @: ["x1'"]))
]) $
unit @Val ("Int" @: ["x'"]))
]) $
unit @Var "m0"
after <- buildExpM $
"m0" <=: store @Int 3 $
"u" <=: (block $
"v'" <=: (block $ switch "v"
[ ("foo" @@: ["a"], "y'" <=: app "foo" ["a"] $
unit @Var "y'")
, ("bar" @@: ["b"], "z'" <=: app "bar" ["b"] $
unit @Var "z'")
, ("Int" @: ["x'"], "u1" <=: block (switch "v1"
[ ("foo" @@: ["a1"], "y1'" <=: app "foo" ["a1"] $
unit @Val ("Int" @: ["y1'"]))
, ("bar" @@: ["b1"], "z1'" <=: app "bar" ["b1"] $
unit @Val ("Float" @: ["z1'"]))
, ("Int" @: ["x1'"], unit @Val ("Int" @: ["x1'"]))
]) $
unit @Var "x'")
]) $
unit @Val ("Int" @: ["v'"])) $
unit @Var "m0"
caseCopyPropagation before `sameAs` after
it "Leave the outher transform the inner" $ do
before <- buildExpM $
"m0" <=: store @Int 3 $
"u" <=: block (switch "v"
[ ("foo" @@: ["a"], "y'" <=: app "foo" ["a"] $
unit @Val ("Int" @: ["y'"]))
, ("bar" @@: ["b"], "z'" <=: app "bar" ["b"] $
unit @Val ("Float" @: ["z'"]))
, ("Int" @: ["x'"], "u1" <=: block (switch "v1"
[ ("foo" @@: ["a1"], "y1'" <=: app "foo" ["a1"] $
unit @Val ("Int" @: ["y1'"]))
, ("bar" @@: ["b1"], "z1'" <=: app "bar" ["b1"] $
unit @Val ("Int" @: ["z1'"]))
, ("Int" @: ["x1'"], unit @Val ("Int" @: ["x1'"]))
]) $
unit @Val ("Int" @: ["x'"]))
]) $
unit @Var "m0"
after <- buildExpM $
"m0" <=: store @Int 3 $
"u" <=: block (switch "v"
[ ("foo" @@: ["a"], "y'" <=: app "foo" ["a"] $
unit @Val ("Int" @: ["y'"]))
, ("bar" @@: ["b"], "z'" <=: app "bar" ["b"] $
unit @Val ("Float" @: ["z'"]))
, ("Int" @: ["x'"], "u1" <=: (block $
"v1'" <=: (block $ switch "v1"
[ ("foo" @@: ["a1"], "y1'" <=: app "foo" ["a1"] $
unit @Var "y1'")
, ("bar" @@: ["b1"], "z1'" <=: app "bar" ["b1"] $
unit @Var "z1'")
, ("Int" @: ["x1'"], unit @Var "x1'")
]) $
unit @Val ("Int" @: ["v1'"])) $
unit @Val ("Int" @: ["x'"]))
]) $
unit @Var "m0"
caseCopyPropagation before `sameAs` after
runTests :: IO ()
runTests = hspec spec