{-# LANGUAGE CPP #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
-- |
-- Module:
--   Reflex.Optimizer
-- Description:
--   This module provides a GHC plugin designed to improve code that uses
--   Reflex.  Currently, it just adds an INLINABLE pragma to any top-level
--   definition that doesn't have an explicit inlining pragma.  In the future,
--   additional optimizations are likely to be added.
module Reflex.Optimizer
  ( plugin
  ) where

#ifdef ghcjs_HOST_OS
import Plugins
#else
import Control.Arrow
import CoreMonad
import Data.String
import GhcPlugins

#if MIN_VERSION_base(4,9,0)
import Prelude hiding ((<>))
#endif

#endif

#ifdef ghcjs_HOST_OS

-- | The GHCJS build of Reflex.Optimizer just throws an error; instead, the version built with GHC should be used.
plugin :: Plugin
plugin = error "The GHCJS build of Reflex.Optimizer cannot be used.  Instead, build with GHC and use the result with GHCJS."

#else

-- | The GHC plugin itself.  See "GhcPlugins" for more details.
plugin :: Plugin
plugin = defaultPlugin { installCoreToDos = install }

install :: [CommandLineOption] -> [CoreToDo] -> CoreM [CoreToDo]
install [] p = do
  liftIO $ putStrLn $ showSDocUnsafe $ ppr p
  let f = \case
        simpl@(CoreDoSimplify _ _) -> [CoreDoSpecialising, simpl]
        x -> [x]
  return $ makeInlinable : concatMap f p
install options@(_:_) p = do
  warnMsg $ "Reflex.Optimizer: ignoring " <> fromString (show $ length options) <> " command-line options"
  install [] p

makeInlinable :: CoreToDo
makeInlinable = CoreDoPluginPass "MakeInlinable" $ \modGuts -> do
  let f v = setIdInfo v $ let i = idInfo v in
        setInlinePragInfo i $ let p = inlinePragInfo i in
        if isDefaultInlinePragma p
        then defaultInlinePragma { inl_inline = Inlinable }
        else p
      newBinds = flip map (mg_binds modGuts) $ \case
        NonRec b e -> NonRec (f b) e
        Rec bes -> Rec $ map (first f) bes
  return $ modGuts { mg_binds = newBinds }

#endif
