@@ -38,7 +38,8 @@ module Language.PureScript.CodeGen.Optimize (
3838) where
3939
4040import Data.Data
41−import Data.Maybe (fromMaybe)
41+import Data.List (nub)
42+import Data.Maybe (fromJust, isJust, fromMaybe)
4243import Data.Generics
4344
4445import Language.PureScript.Names
@@ -95,6 +96,7 @@ isReassigned var1 = everything (||) (mkQ False check)
9596 check :: JS -> Bool
9697 check (JSFunction _ args _) | var1 `elem` args = True
9798 check (JSVariableIntroduction arg _) | var1 == arg = True
99+ check (JSAssignment (JSVar arg) _) | var1 == arg = True
98100 check _ = False
99101
100102isRebound :: (Data d) => JS -> d -> Bool
@@ -254,52 +256,136 @@ tco' = everywhere (mkT convert)
254256 isSelfCall _ _ = False
255257
256258magicDo :: Options -> JS -> JS
257−magicDo opts | optionsMagicDo opts = magicDo'
259+magicDo opts | optionsMagicDo opts = inlineST . magicDo'
258260 | otherwise = id
259261
262+-- |
263+-- Inline type class dictionaries for >>= and return for the Eff monad
264+--
265+-- E.g.
266+--
267+-- Prelude[">>="](dict)(m1)(function(x) {
268+-- return ...;
269+-- })
270+--
271+-- becomes
272+--
273+-- function __do {
274+-- var x = m1();
275+-- ...
276+-- }
277+--
260278magicDo' :: JS -> JS
261279magicDo' = everywhere (mkT undo) . everywhere' (mkT convert)
262280 where
281+ -- The name of the function block which is added to denote a do block
263282 fnName = "__do"
264−
283+ -- Desugar monomorphic calls to >>= and return for the Eff monad
265284 convert :: JS -> JS
285+ -- Desugar return
266286 convert (JSApp (JSApp ret [val]) []) | isReturn ret = val
287+ -- Desugae >>
267288 convert (JSApp (JSApp bind [m]) [JSFunction Nothing ["_"] (JSBlock [JSReturn ret])]) | isBind bind =
268289 JSFunction (Just fnName) [] $ JSBlock [ JSApp m [], JSReturn (JSApp ret []) ]
290+ -- Desugar >>=
269291 convert (JSApp (JSApp bind [m]) [JSFunction Nothing [arg] (JSBlock [JSReturn ret])]) | isBind bind =
270292 JSFunction (Just fnName) [] $ JSBlock [ JSVariableIntroduction arg (Just (JSApp m [])), JSReturn (JSApp ret []) ]
293+ -- Desugar untilE
294+ convert (JSApp (JSApp f [arg]) []) | isEffFunc "untilE" f =
295+ JSApp (JSFunction Nothing [] (JSBlock [ JSWhile (JSUnary Not (JSApp arg [])) (JSBlock []), JSReturn (JSObjectLiteral []) ])) []
296+ -- Desugar whileE
297+ convert (JSApp (JSApp (JSApp f [arg1]) [arg2]) []) | isEffFunc "whileE" f =
298+ JSApp (JSFunction Nothing [] (JSBlock [ JSWhile (JSApp arg1 []) (JSBlock [ JSApp arg2 [] ]), JSReturn (JSObjectLiteral []) ])) []
271299 convert other = other
272−
300+ -- Check if an expression represents a monomorphic call to >>= for the Eff monad
273301 isBind (JSApp bindPoly [effDict]) | isBindPoly bindPoly && isEffDict effDict = True
274302 isBind _ = False
275−
303+ -- Check if an expression represents a monomorphic call to return for the Eff monad
276304 isReturn (JSApp retPoly [effDict]) | isRetPoly retPoly && isEffDict effDict = True
277305 isReturn _ = False
278−
306+ -- Check if an expression represents the polymorphic >>= function
279307 isBindPoly (JSAccessor prop (JSAccessor "Prelude" (JSVar "_ps"))) | prop == identToJs (Op ">>=") = True
280308 isBindPoly (JSIndexer (JSStringLiteral ">>=") (JSAccessor "Prelude" (JSVar "_ps"))) = True
281309 isBindPoly _ = False
282−
310+ -- Check if an expression represents the polymorphic return function
283311 isRetPoly (JSAccessor "$return" (JSAccessor "Prelude" (JSVar "_ps"))) = True
284312 isRetPoly (JSIndexer (JSStringLiteral "return") (JSAccessor "Prelude" (JSVar "_ps"))) = True
285313 isRetPoly _ = False
286−
287− prelude = ModuleName (ProperName "Prelude")
288− effModule = ModuleName (ProperName "Eff")
289−
314+ -- Check if an expression represents a function in the Ef module
315+ isEffFunc name (JSAccessor name' (JSAccessor "Eff" (JSVar "_ps"))) | name == name' = True
316+ isEffFunc _ _ = False
317+ -- Module names
318+ prelude = ModuleName [ProperName "Prelude"]
319+ effModule = ModuleName [ProperName "Eff"]
320+ -- The name of the type class dictionary for the Monad Eff instance
290321 Right (Ident effDictName) = mkDictionaryValueName
291322 effModule
292323 (Qualified (Just prelude) (ProperName "Monad"))
293324 (TypeConstructor (Qualified (Just effModule) (ProperName "Eff")))
294−
325+ -- Check if an expression represents the Monad Eff dictionary
295326 isEffDict (JSApp (JSVar ident) [JSObjectLiteral []]) | ident == effDictName = True
296327 isEffDict (JSApp (JSAccessor prop (JSAccessor "Eff" (JSVar "_ps"))) [JSObjectLiteral []]) | prop == effDictName = True
297328 isEffDict _ = False
298−
329+ -- Remove __do function applications which remain after desugaring
299330 undo :: JS -> JS
300331 undo (JSReturn (JSApp (JSFunction (Just ident) [] body) [])) | ident == fnName = body
301332 undo other = other
302333
334+-- |
335+-- Inline functions in the ST module
336+--
337+inlineST :: JS -> JS
338+inlineST = everywhere (mkT convertBlock)
339+ where
340+ -- Look for runST blocks and inline the STRefs there.
341+ -- If all STRefs are used in the scope of the same runST, only using { read, write, modify }STRef then
342+ -- we can be more aggressive about inlining, and actually turn STRefs into local variables.
343+ convertBlock (JSApp f [arg]) | isSTFunc "runST" f || isSTFunc "runSTArray" f =
344+ let refs = nub . findSTRefsIn $ arg
345+ usages = findAllSTUsagesIn arg
346+ allUsagesAreLocalVars = all (\u -> let v = toVar u in isJust v && fromJust v `elem` refs) usages
347+ localVarsDoNotEscape = all (\r -> length (r `appearingIn` arg) == length (filter (\u -> let v = toVar u in v == Just r) usages)) refs
348+ in everywhere (mkT $ convert allUsagesAreLocalVars) arg
349+ convertBlock other = other
350+ -- Convert a block in a safe way, preserving object wrappers of references,
351+ -- or in a more aggressive way, turning wrappers into local variables depending on the
352+ -- agg(ressive) parameter.
353+ convert agg (JSApp (JSApp f [arg]) []) | isSTFunc "newSTRef" f =
354+ if agg then arg else JSObjectLiteral [("value", arg)]
355+ convert agg (JSApp (JSApp f [ref]) []) | isSTFunc "readSTRef" f =
356+ if agg then ref else JSAccessor "value" ref
357+ convert agg (JSApp (JSApp (JSApp f [ref]) [arg]) []) | isSTFunc "writeSTRef" f =
358+ if agg then JSAssignment ref arg else JSAssignment (JSAccessor "value" ref) arg
359+ convert agg (JSApp (JSApp (JSApp f [ref]) [func]) []) | isSTFunc "modifySTRef" f =
360+ if agg then JSAssignment ref (JSApp func [ref]) else JSAssignment (JSAccessor "value" ref) (JSApp func [JSAccessor "value" ref])
361+ convert _ (JSApp (JSApp (JSApp f [arr]) [i]) []) | isSTFunc "peekSTArray" f =
362+ (JSIndexer i arr)
363+ convert _ (JSApp (JSApp (JSApp (JSApp f [arr]) [i]) [val]) []) | isSTFunc "pokeSTArray" f =
364+ JSAssignment (JSIndexer i arr) val
365+ convert _ other = other
366+ -- Check if an expression represents a function in the ST module
367+ isSTFunc name (JSAccessor name' (JSAccessor "ST" (JSVar "_ps"))) | name == name' = True
368+ isSTFunc _ _ = False
369+ -- Find all ST Refs initialized in this block
370+ findSTRefsIn = everything (++) (mkQ [] isSTRef)
371+ where
372+ isSTRef (JSVariableIntroduction ident (Just (JSApp (JSApp f [arg]) []))) | isSTFunc "newSTRef" f = [ident]
373+ isSTRef _ = []
374+ -- Find all STRefs used as arguments to readSTRef, writeSTRef, modifySTRef
375+ findAllSTUsagesIn = everything (++) (mkQ [] isSTUsage)
376+ where
377+ isSTUsage (JSApp (JSApp f [ref]) []) | isSTFunc "readSTRef" f = [ref]
378+ isSTUsage (JSApp (JSApp (JSApp f [ref]) [_]) []) | isSTFunc "writeSTRef" f || isSTFunc "modifySTRef" f = [ref]
379+ isSTUsage _ = []
380+ -- Find all uses of a variable
381+ appearingIn ref = everything (++) (mkQ [] isVar)
382+ where
383+ isVar e@(JSVar v) | v == ref = [e]
384+ isVar _ = []
385+ -- Convert a JS value to a String if it is a JSVar
386+ toVar (JSVar v) = Just v
387+ toVar _ = Nothing
388+
303389collapseNestedBlocks :: JS -> JS
304390collapseNestedBlocks = everywhere (mkT collapse)
305391 where
@@ -382,7 +468,7 @@ inlineCommonOperators = applyAll
382468 isOpDict className ty (JSApp (JSAccessor prop (JSAccessor "Prelude" (JSVar "_ps"))) [JSObjectLiteral []]) | prop == dictName = True
383469 where
384470 Right (Ident dictName) = mkDictionaryValueName
385− (ModuleName (ProperName "Prim"))
386− (Qualified (Just (ModuleName (ProperName "Prelude"))) (ProperName className))
471+ (ModuleName [ProperName "Prim"])
472+ (Qualified (Just (ModuleName [ProperName "Prelude"])) (ProperName className))
387473 ty
388474 isOpDict _ _ _ = False