@@ -3,87 +3,166 @@ module Language.PureScript.CoreImp.Optimizer.TCO (tco) where
33
44import Prelude.Compat
55
6- import Data.Text (Text )
6+ import Control.Applicative (empty , liftA2 )
7+ import Control.Monad (guard )
8+ import Control.Monad.State (State , evalState , get , modify )
9+ import Data.Foldable (foldr )
10+ import Data.Functor (($>) , (<&>) )
11+ import qualified Data.Set as S
12+ import Data.Text (Text , pack )
713import qualified Language.PureScript.Constants.Prim as C
814import Language.PureScript.CoreImp.AST
915import Language.PureScript.AST.SourcePos (SourceSpan )
1016import Safe (headDef , tailSafe )
1117
1218-- | Eliminate tail calls
1319tco :: AST -> AST
14- tco = everywhere convert where
20+ tco = flip evalState 0 . everywhereTopDownM convert where
1521 tcoVar :: Text -> Text
1622 tcoVar arg = " $tco_var_" <> arg
1723
1824 copyVar :: Text -> Text
1925 copyVar arg = " $copy_" <> arg
2026
21- tcoDone :: Text
22- tcoDone = " $tco_done"
27+ tcoDoneM :: State Int Text
28+ tcoDoneM = get <&> \ count -> " $tco_done" <>
29+ if count == 0 then " " else pack . show $ count
2330
2431 tcoLoop :: Text
2532 tcoLoop = " $tco_loop"
2633
2734 tcoResult :: Text
2835 tcoResult = " $tco_result"
2936
30- convert :: AST -> AST
37+ convert :: AST -> State Int AST
3138 convert (VariableIntroduction ss name (Just fn@ Function {}))
32- | isTailRecursive name body'
33- = VariableIntroduction ss name ( Just ( replace ( toLoop name outerArgs innerArgs body')))
39+ | Just trFns <- findTailRecursiveFns name arity body'
40+ = VariableIntroduction ss name . Just . replace <$> toLoop trFns name arity outerArgs innerArgs body'
3441 where
3542 innerArgs = headDef [] argss
3643 outerArgs = concat . reverse $ tailSafe argss
37- (argss, body', replace) = collectAllFunctionArgs [] id fn
38- convert js = js
39-
40- collectAllFunctionArgs :: [[Text ]] -> (AST -> AST ) -> AST -> ([[Text ]], AST , AST -> AST )
41- collectAllFunctionArgs allArgs f (Function s1 ident args (Block s2 (body@ (Return _ _): _))) =
42- collectAllFunctionArgs (args : allArgs) (\ b -> f (Function s1 ident (map copyVar args) (Block s2 [b]))) body
43- collectAllFunctionArgs allArgs f (Function ss ident args body@ (Block _ _)) =
44- (args : allArgs, body, f . Function ss ident (map copyVar args))
45- collectAllFunctionArgs allArgs f (Return s1 (Function s2 ident args (Block s3 [body]))) =
46- collectAllFunctionArgs (args : allArgs) (\ b -> f (Return s1 (Function s2 ident (map copyVar args) (Block s3 [b])))) body
47- collectAllFunctionArgs allArgs f (Return s1 (Function s2 ident args body@ (Block _ _))) =
48- (args : allArgs, body, f . Return s1 . Function s2 ident (map copyVar args))
49- collectAllFunctionArgs allArgs f body = (allArgs, body, f)
50-
51- isTailRecursive :: Text -> AST -> Bool
52- isTailRecursive ident js = countSelfReferences js > 0 && allInTailPosition js where
53- countSelfReferences = everything (+) match where
54- match :: AST -> Int
55- match (Var _ ident') | ident == ident' = 1
56- match _ = 0
44+ arity = length argss
45+ -- ^ this is the number of calls, not the number of arguments, if there's
46+ -- ever a practical difference.
47+ (argss, body', replace) = topCollectAllFunctionArgs [] id fn
48+ convert js = pure js
49+
50+ rewriteFunctionsWith :: ([Text ] -> [Text ]) -> [[Text ]] -> (AST -> AST ) -> AST -> ([[Text ]], AST , AST -> AST )
51+ rewriteFunctionsWith argMapper = collectAllFunctionArgs
52+ where
53+ collectAllFunctionArgs allArgs f (Function s1 ident args (Block s2 (body@ (Return _ _): _))) =
54+ collectAllFunctionArgs (args : allArgs) (\ b -> f (Function s1 ident (argMapper args) (Block s2 [b]))) body
55+ collectAllFunctionArgs allArgs f (Function ss ident args body@ (Block _ _)) =
56+ (args : allArgs, body, f . Function ss ident (argMapper args))
57+ collectAllFunctionArgs allArgs f (Return s1 (Function s2 ident args (Block s3 [body]))) =
58+ collectAllFunctionArgs (args : allArgs) (\ b -> f (Return s1 (Function s2 ident (argMapper args) (Block s3 [b])))) body
59+ collectAllFunctionArgs allArgs f (Return s1 (Function s2 ident args body@ (Block _ _))) =
60+ (args : allArgs, body, f . Return s1 . Function s2 ident (argMapper args))
61+ collectAllFunctionArgs allArgs f body = (allArgs, body, f)
62+
63+ topCollectAllFunctionArgs :: [[Text ]] -> (AST -> AST ) -> AST -> ([[Text ]], AST , AST -> AST )
64+ topCollectAllFunctionArgs = rewriteFunctionsWith (map copyVar)
65+
66+ innerCollectAllFunctionArgs :: [[Text ]] -> (AST -> AST ) -> AST -> ([[Text ]], AST , AST -> AST )
67+ innerCollectAllFunctionArgs = rewriteFunctionsWith id
68+
69+ countReferences :: Text -> AST -> Int
70+ countReferences ident = everything (+) match where
71+ match :: AST -> Int
72+ match (Var _ ident') | ident == ident' = 1
73+ match _ = 0
74+
75+ -- If `ident` is a tail-recursive function, returns a set of identifiers
76+ -- that are locally bound to functions participating in the tail recursion.
77+ -- Otherwise, returns Nothing.
78+ findTailRecursiveFns :: Text -> Int -> AST -> Maybe (S. Set Text )
79+ findTailRecursiveFns ident arity js = guard (countReferences ident js > 0 ) *> go (S. empty, S. singleton (ident, arity))
80+ where
81+
82+ go :: (S. Set Text , S. Set (Text , Int )) -> Maybe (S. Set Text )
83+ go (known, required) =
84+ case S. minView required of
85+ Just (r, required') -> do
86+ required'' <- findTailPositionDeps r js
87+ go (S. insert (fst r) known, required' <> (S. filter (not . (`S.member` known) . fst ) required''))
88+ Nothing ->
89+ pure known
90+
91+ -- Returns set of identifiers (with their arities) that need to be used
92+ -- exclusively in tail calls using their full arity in order for this
93+ -- identifier to be considered in tail position (or Nothing if this
94+ -- identifier is used somewhere not as a tail call with full arity).
95+ findTailPositionDeps :: (Text , Int ) -> AST -> Maybe (S. Set (Text , Int ))
96+ findTailPositionDeps (ident, arity) js = allInTailPosition js where
97+ countSelfReferences = countReferences ident
5798
5899 allInTailPosition (Return _ expr)
59- | isSelfCall ident expr = countSelfReferences expr == 1
60- | otherwise = countSelfReferences expr == 0
100+ | isSelfCall ident arity expr = guard ( countSelfReferences expr == 1 ) $> S. empty
101+ | otherwise = guard ( countSelfReferences expr == 0 ) $> S. empty
61102 allInTailPosition (While _ js1 body)
62- = countSelfReferences js1 == 0 && allInTailPosition body
103+ = guard ( countSelfReferences js1 == 0 ) *> allInTailPosition body
63104 allInTailPosition (For _ _ js1 js2 body)
64- = countSelfReferences js1 == 0 && countSelfReferences js2 == 0 && allInTailPosition body
105+ = guard ( countSelfReferences js1 == 0 && countSelfReferences js2 == 0 ) *> allInTailPosition body
65106 allInTailPosition (ForIn _ _ js1 body)
66- = countSelfReferences js1 == 0 && allInTailPosition body
107+ = guard ( countSelfReferences js1 == 0 ) *> allInTailPosition body
67108 allInTailPosition (IfElse _ js1 body el)
68- = countSelfReferences js1 == 0 && allInTailPosition body && all allInTailPosition el
109+ = guard ( countSelfReferences js1 == 0 ) *> liftA2 mappend ( allInTailPosition body) (foldMapA allInTailPosition el)
69110 allInTailPosition (Block _ body)
70- = all allInTailPosition body
111+ = foldMapA allInTailPosition body
71112 allInTailPosition (Throw _ js1)
72- = countSelfReferences js1 == 0
113+ = guard ( countSelfReferences js1 == 0 ) $> S. empty
73114 allInTailPosition (ReturnNoResult _)
74- = True
75- allInTailPosition (VariableIntroduction _ _ js1)
76- = all ((== 0 ) . countSelfReferences) js1
115+ = pure S. empty
116+ allInTailPosition (VariableIntroduction _ _ Nothing )
117+ = pure S. empty
118+ allInTailPosition (VariableIntroduction _ ident' (Just js1))
119+ | countSelfReferences js1 == 0 = pure S. empty
120+ | Function _ Nothing _ _ <- js1
121+ , (argss, body, _) <- innerCollectAllFunctionArgs [] id js1
122+ = S. insert (ident', length argss) <$> allInTailPosition body
123+ | otherwise = empty
77124 allInTailPosition (Assignment _ _ js1)
78- = countSelfReferences js1 == 0
125+ = guard ( countSelfReferences js1 == 0 ) $> S. empty
79126 allInTailPosition (Comment _ _ js1)
80127 = allInTailPosition js1
81128 allInTailPosition _
82- = False
83-
84- toLoop :: Text -> [Text ] -> [Text ] -> AST -> AST
85- toLoop ident outerArgs innerArgs js =
86- Block rootSS $
129+ = empty
130+
131+ toLoop :: S. Set Text -> Text -> Int -> [Text ] -> [Text ] -> AST -> State Int AST
132+ toLoop trFns ident arity outerArgs innerArgs js = do
133+ tcoDone <- tcoDoneM
134+ modify (+ 1 )
135+
136+ let
137+ markDone :: Maybe SourceSpan -> AST
138+ markDone ss = Assignment ss (Var ss tcoDone) (BooleanLiteral ss True )
139+
140+ loopify :: AST -> AST
141+ loopify (Return ss ret)
142+ | isSelfCall ident arity ret =
143+ let
144+ allArgumentValues = concat $ collectArgs [] ret
145+ in
146+ Block ss $
147+ zipWith (\ val arg ->
148+ Assignment ss (Var ss (tcoVar arg)) val) allArgumentValues outerArgs
149+ ++ zipWith (\ val arg ->
150+ Assignment ss (Var ss (copyVar arg)) val) (drop (length outerArgs) allArgumentValues) innerArgs
151+ ++ [ ReturnNoResult ss ]
152+ | isIndirectSelfCall ret = Return ss ret
153+ | otherwise = Block ss [ markDone ss, Return ss ret ]
154+ loopify (ReturnNoResult ss) = Block ss [ markDone ss, ReturnNoResult ss ]
155+ loopify (While ss cond body) = While ss cond (loopify body)
156+ loopify (For ss i js1 js2 body) = For ss i js1 js2 (loopify body)
157+ loopify (ForIn ss i js1 body) = ForIn ss i js1 (loopify body)
158+ loopify (IfElse ss cond body el) = IfElse ss cond (loopify body) (fmap loopify el)
159+ loopify (Block ss body) = Block ss (map loopify body)
160+ loopify (VariableIntroduction ss f (Just fn@ (Function _ Nothing _ _)))
161+ | (_, body, replace) <- innerCollectAllFunctionArgs [] id fn
162+ , f `S.member` trFns = VariableIntroduction ss f (Just (replace (loopify body)))
163+ loopify other = other
164+
165+ pure $ Block rootSS $
87166 map (\ arg -> VariableIntroduction rootSS (tcoVar arg) (Just (Var rootSS (copyVar arg)))) outerArgs ++
88167 [ VariableIntroduction rootSS tcoDone (Just (BooleanLiteral rootSS False ))
89168 , VariableIntroduction rootSS tcoResult Nothing
@@ -96,38 +175,22 @@ tco = everywhere convert where
96175 where
97176 rootSS = Nothing
98177
99- loopify :: AST -> AST
100- loopify (Return ss ret)
101- | isSelfCall ident ret =
102- let
103- allArgumentValues = concat $ collectArgs [] ret
104- in
105- Block ss $
106- zipWith (\ val arg ->
107- Assignment ss (Var ss (tcoVar arg)) val) allArgumentValues outerArgs
108- ++ zipWith (\ val arg ->
109- Assignment ss (Var ss (copyVar arg)) val) (drop (length outerArgs) allArgumentValues) innerArgs
110- ++ [ ReturnNoResult ss ]
111- | otherwise = Block ss [ markDone ss, Return ss ret ]
112- loopify (ReturnNoResult ss) = Block ss [ markDone ss, ReturnNoResult ss ]
113- loopify (While ss cond body) = While ss cond (loopify body)
114- loopify (For ss i js1 js2 body) = For ss i js1 js2 (loopify body)
115- loopify (ForIn ss i js1 body) = ForIn ss i js1 (loopify body)
116- loopify (IfElse ss cond body el) = IfElse ss cond (loopify body) (fmap loopify el)
117- loopify (Block ss body) = Block ss (map loopify body)
118- loopify other = other
119-
120- markDone :: Maybe SourceSpan -> AST
121- markDone ss = Assignment ss (Var ss tcoDone) (BooleanLiteral ss True )
122-
123178 collectArgs :: [[AST ]] -> AST -> [[AST ]]
124179 collectArgs acc (App _ fn [] ) =
125180 -- count 0-argument applications as single-argument so we get the correct number of args
126181 collectArgs ([Var Nothing C. undefined ] : acc) fn
127182 collectArgs acc (App _ fn args') = collectArgs (args' : acc) fn
128183 collectArgs acc _ = acc
129184
130- isSelfCall :: Text -> AST -> Bool
131- isSelfCall ident (App _ (Var _ ident') _) = ident == ident'
132- isSelfCall ident (App _ fn _) = isSelfCall ident fn
133- isSelfCall _ _ = False
185+ isIndirectSelfCall :: AST -> Bool
186+ isIndirectSelfCall (App _ (Var _ ident') _) = ident' `S.member` trFns
187+ isIndirectSelfCall (App _ fn _) = isIndirectSelfCall fn
188+ isIndirectSelfCall _ = False
189+
190+ isSelfCall :: Text -> Int -> AST -> Bool
191+ isSelfCall ident 1 (App _ (Var _ ident') _) = ident == ident'
192+ isSelfCall ident arity (App _ fn _) = isSelfCall ident (arity - 1 ) fn
193+ isSelfCall _ _ _ = False
194+
195+ foldMapA :: (Applicative f , Monoid w , Foldable t ) => (a -> f w ) -> t a -> f w
196+ foldMapA f = foldr (liftA2 mappend . f) (pure mempty )
0 commit comments