content: -- | This module optimizes code in the simplified-JavaScript intermediate representation.
  --
  -- The following optimizations are supported:
  --
  --  * Collapsing nested blocks
  --
  --  * Tail call elimination
  --
  --  * Inlining of (>>=) and ret for the Eff monad
  --
  --  * Removal of unnecessary thunks
  --
  --  * Eta conversion
  --
  --  * Inlining variables
  --
  --  * Inline Prelude.($), Prelude.(#), Prelude.(++), Prelude.(!!)
  --
  --  * Inlining primitive JavaScript operators
  module Language.PureScript.CoreImp.Optimizer (optimize) where
  
  import Prelude
  
  import Data.Text (Text)
  
  import Control.Monad.Supply.Class (MonadSupply)
  import Language.PureScript.CoreImp.AST (AST(..), InitializerEffects(..))
  import Language.PureScript.CoreImp.Optimizer.Blocks (collapseNestedBlocks, collapseNestedIfs)
  import Language.PureScript.CoreImp.Optimizer.Common (applyAll, replaceIdents)
  import Language.PureScript.CoreImp.Optimizer.Inliner (etaConvert, evaluateIifes, inlineCommonOperators, inlineCommonValues, inlineFnComposition, inlineFnIdentity, inlineUnsafeCoerce, inlineUnsafePartial, inlineVariables, unThunk)
  import Language.PureScript.CoreImp.Optimizer.MagicDo (inlineST, magicDoEff, magicDoEffect, magicDoST)
  import Language.PureScript.CoreImp.Optimizer.TCO (tco)
  import Language.PureScript.CoreImp.Optimizer.Unused (removeCodeAfterReturnStatements, removeUndefinedApp, removeUnusedEffectFreeVars)
  
  -- | Apply a series of optimizer passes to simplified JavaScript code
  optimize :: forall m. MonadSupply m => [Text] -> [[AST]] -> m [[AST]]
  optimize exps jsDecls = removeUnusedEffectFreeVars exps <$> traverse (traverse go) jsDecls
    where
      go :: AST -> m AST
      go js = do
        js' <- untilFixedPoint (inlineFnComposition expander . inlineFnIdentity expander . inlineUnsafeCoerce . inlineUnsafePartial . tidyUp . applyAll
          [ inlineCommonValues expander
          , inlineCommonOperators expander
          ]) js
        untilFixedPoint (return . tidyUp) . tco . inlineST
          =<< untilFixedPoint (return . magicDoST expander)
          =<< untilFixedPoint (return . magicDoEff expander)
          =<< untilFixedPoint (return . magicDoEffect expander) js'
  
      tidyUp :: AST -> AST
      tidyUp = applyAll
        [ collapseNestedBlocks
        , collapseNestedIfs
        , removeCodeAfterReturnStatements
        , removeUndefinedApp
        , unThunk
        , etaConvert
        , evaluateIifes
        , inlineVariables
        ]
  
      expander = buildExpander (concat jsDecls)
  
  untilFixedPoint :: (Monad m, Eq a) => (a -> m a) -> a -> m a
  untilFixedPoint f = go
    where
    go a = do
     a' <- f a
     if a' == a then return a' else go a'
  
  -- |
  -- Take all top-level ASTs and return a function for expanding top-level
  -- variables during the various inlining steps in `optimize`.
  --
  -- Everything that gets inlined as an optimization is of a form that would
  -- have been lifted to a top-level binding during CSE, so for purposes of
  -- inlining we can save some time by only expanding variables bound at that
  -- level and not worrying about any inner scopes.
  --
  buildExpander :: [AST] -> AST -> AST
  buildExpander = replaceIdents . foldr go []
    where
    go = \case
      VariableIntroduction _ name (Just (NoEffects, e)) -> ((name, e) :)
      _ -> id
  