@@ -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
@@ -288,6 +289,12 @@ magicDo' = everywhere (mkT undo) . everywhere' (mkT convert)
288289 -- Desugar >>=
289290 convert (JSApp (JSApp bind [m]) [JSFunction Nothing [arg] (JSBlock [JSReturn ret])]) | isBind bind =
290291 JSFunction (Just fnName) [] $ JSBlock [ JSVariableIntroduction arg (Just (JSApp m [])), JSReturn (JSApp ret []) ]
292+ -- Desugar untilE
293+ convert (JSApp (JSApp f [arg]) []) | isEffFunc "untilE" f =
294+ JSApp (JSFunction Nothing [] (JSBlock [ JSWhile (JSUnary Not (JSApp arg [])) (JSBlock []), JSReturn (JSObjectLiteral []) ])) []
295+ -- Desugar whileE
296+ convert (JSApp (JSApp (JSApp f [arg1]) [arg2]) []) | isEffFunc "whileE" f =
297+ JSApp (JSFunction Nothing [] (JSBlock [ JSWhile (JSApp arg1 []) (JSBlock [ JSApp arg2 [] ]), JSReturn (JSObjectLiteral []) ])) []
291298 convert other = other
292299 -- Check if an expression represents a monomorphic call to >>= for the Eff monad
293300 isBind (JSApp bindPoly [effDict]) | isBindPoly bindPoly && isEffDict effDict = True
@@ -303,6 +310,9 @@ magicDo' = everywhere (mkT undo) . everywhere' (mkT convert)
303310 isRetPoly (JSAccessor "$return" (JSAccessor "Prelude" (JSVar "_ps"))) = True
304311 isRetPoly (JSIndexer (JSStringLiteral "return") (JSAccessor "Prelude" (JSVar "_ps"))) = True
305312 isRetPoly _ = False
313+ -- Check if an expression represents a function in the Ef module
314+ isEffFunc name (JSAccessor name' (JSAccessor "Eff" (JSVar "_ps"))) | name == name' = True
315+ isEffFunc _ _ = False
306316 -- Module names
307317 prelude = ModuleName (ProperName "Prelude")
308318 effModule = ModuleName (ProperName "Eff")
@@ -324,20 +334,56 @@ magicDo' = everywhere (mkT undo) . everywhere' (mkT convert)
324334-- Inline functions in the ST module
325335--
326336inlineST :: JS -> JS
327−inlineST = everywhere (mkT convert)
337+inlineST = everywhere (mkT convertBlock)
328338 where
329− convert (JSApp (JSApp f [arg]) []) | isSTFunc "newSTRef" f =
339+ -- Look for runST blocks and inline the STRefs there.
340+ -- If all STRefs are used in the scope of the same runST, only using { read, write, modify }STRef then
341+ -- we can be more aggressive about inlining, and actually turn STRefs into local variables.
342+ convertBlock (JSApp f [arg]) | isSTFunc "runST" f =
343+ let refs = nub . findSTRefsIn $ arg
344+ usages = findAllSTUsagesIn arg
345+ allUsagesAreLocalVars = all (\u -> let v = toVar u in isJust v && fromJust v `elem` refs) usages
346+ localVarsDoNotEscape = all (\r -> length (r `appearingIn` arg) == length (filter (\u -> let v = toVar u in v == Just r) usages)) refs
347+ in everywhere (mkT $ if allUsagesAreLocalVars then convertAggressive else convertSafe) arg
348+ convertBlock other = other
349+ -- Convert a block in a safe way, preserving object wrappers of references
350+ convertSafe (JSApp (JSApp f [arg]) []) | isSTFunc "newSTRef" f =
330351 JSObjectLiteral [("value", arg)]
331− convert (JSApp (JSApp f [ref]) []) | isSTFunc "readSTRef" f =
352+ convertSafe (JSApp (JSApp f [ref]) []) | isSTFunc "readSTRef" f =
332353 JSAccessor "value" ref
333− convert (JSApp (JSApp (JSApp f [ref]) [arg]) []) | isSTFunc "writeSTRef" f =
354+ convertSafe (JSApp (JSApp (JSApp f [ref]) [arg]) []) | isSTFunc "writeSTRef" f =
334355 JSAssignment (JSAccessor "value" ref) arg
335− convert (JSApp (JSApp (JSApp f [ref]) [func]) []) | isSTFunc "modifySTRef" f =
356+ convertSafe (JSApp (JSApp (JSApp f [ref]) [func]) []) | isSTFunc "modifySTRef" f =
336357 JSAssignment (JSAccessor "value" ref) (JSApp func [JSAccessor "value" ref])
337− convert other = other
358+ convertSafe other = other
359+ -- Convert a block in a more agressive way, unwrapping object wrappers into local variables
360+ convertAggressive (JSApp (JSApp f [arg]) []) | isSTFunc "newSTRef" f = arg
361+ convertAggressive (JSApp (JSApp f [ref]) []) | isSTFunc "readSTRef" f = ref
362+ convertAggressive (JSApp (JSApp (JSApp f [ref]) [arg]) []) | isSTFunc "writeSTRef" f = JSAssignment ref arg
363+ convertAggressive (JSApp (JSApp (JSApp f [ref]) [func]) []) | isSTFunc "modifySTRef" f = JSAssignment ref (JSApp func [ref])
364+ convertAggressive other = other
338365 -- Check if an expression represents a function in the ST module
339366 isSTFunc name (JSAccessor name' (JSAccessor "ST" (JSVar "_ps"))) | name == name' = True
340367 isSTFunc _ _ = False
368+ -- Find all ST Refs initialized in this block
369+ findSTRefsIn = everything (++) (mkQ [] isSTRef)
370+ where
371+ isSTRef (JSVariableIntroduction ident (Just (JSApp (JSApp f [arg]) []))) | isSTFunc "newSTRef" f = [ident]
372+ isSTRef _ = []
373+ -- Find all STRefs used as arguments to readSTRef, writeSTRef, modifySTRef
374+ findAllSTUsagesIn = everything (++) (mkQ [] isSTUsage)
375+ where
376+ isSTUsage (JSApp (JSApp f [ref]) []) | isSTFunc "readSTRef" f = [ref]
377+ isSTUsage (JSApp (JSApp (JSApp f [ref]) [_]) []) | isSTFunc "writeSTRef" f || isSTFunc "modifySTRef" f = [ref]
378+ isSTUsage _ = []
379+ -- Find all uses of a variable
380+ appearingIn ref = everything (++) (mkQ [] isVar)
381+ where
382+ isVar e@(JSVar v) | v == ref = [e]
383+ isVar _ = []
384+ -- Convert a JS value to a String if it is a JSVar
385+ toVar (JSVar v) = Just v
386+ toVar _ = Nothing
341387
342388collapseNestedBlocks :: JS -> JS
343389collapseNestedBlocks = everywhere (mkT collapse)