hermit-1.0.0.0: src/HERMIT/Kernel.hs
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
module HERMIT.Kernel
( -- * The HERMIT Kernel
AST
, firstAST
, ASTMap
, Kernel
, KernelEnv(..)
, hermitKernel
, CommitMsg(..)
-- ** Kernel Interface
, resumeK
, abortK
, applyK
, queryK
, deleteK
, listK
, tellK
) where
import Prelude hiding (lookup, null)
import Control.Concurrent
import Control.Monad
import Control.Monad.IO.Class
import Data.IORef
import Data.Map
import Data.Typeable
import HERMIT.Context
import HERMIT.External
import HERMIT.GHC hiding (singleton, empty)
import HERMIT.Kure
import HERMIT.Lemma
import HERMIT.Monad
-- | A 'Kernel' is a repository for complete Core syntax trees ('ModGuts') and Lemmas.
data Kernel = Kernel
{ -- | Halt the 'Kernel' and return control to GHC, which compiles the specified 'AST'.
resumeK :: forall m. MonadIO m => AST -> m ()
-- | Halt the 'Kernel' and abort GHC without compiling.
, abortK :: forall m. MonadIO m => m ()
-- | Apply a 'Rewrite' to the specified 'AST' and return a handle to the resulting 'AST'.
, applyK :: forall m. (MonadIO m, MonadCatch m)
=> RewriteH ModGuts -> CommitMsg -> KernelEnv -> AST -> m AST
-- | Apply a 'TransformH' to the 'AST', return the resulting value, and potentially a new 'AST'.
, queryK :: forall m a. (MonadIO m, MonadCatch m)
=> TransformH ModGuts a -> CommitMsg -> KernelEnv -> AST -> m (AST,a)
-- | Delete the internal record of the specified 'AST'.
, deleteK :: forall m. MonadIO m => AST -> m ()
-- | List all the 'AST's tracked by the 'Kernel', including version data.
, listK :: forall m. MonadIO m => m [(AST,Maybe String, Maybe AST)]
-- | Log a new AST with same Lemmas/ModGuts as given AST.
, tellK :: forall m. (MonadIO m, MonadCatch m) => String -> AST -> m AST
}
data CommitMsg = Always String | Changed String | Never
msg :: CommitMsg -> Maybe String
msg Never = Nothing
msg (Always s) = Just s
msg (Changed s) = Just s
-- | A /handle/ for a specific version of the 'ModGuts'.
newtype AST = AST Int -- ^ Currently 'AST's are identified by an 'Int' label.
deriving (Eq, Ord, Typeable)
firstAST :: AST
firstAST = AST 0
-- for succ
instance Enum AST where
toEnum = AST
fromEnum (AST i) = i
instance Show AST where
show (AST i) = show i
instance Read AST where
readsPrec p s = [ (AST i,s') | (i,s') <- readsPrec p s ]
instance Extern AST where
type Box AST = AST
box i = i
unbox i = i
data ASTMap = ASTMap { astNext :: AST
, astMap :: Map AST KernelState
}
emptyASTMap :: ASTMap
emptyASTMap = ASTMap firstAST empty
data KernelState = KernelState { ksLemmas :: Lemmas
, ksGuts :: ModGuts
, _ksParent :: Maybe AST
, _ksCommit :: Maybe String
}
data KernelEnv = KernelEnv { kEnvChan :: KEnvMessage -> HermitM () }
-- | Internal API. The 'Kernel' object wraps these calls.
data Msg where
Apply :: AST -> (KernelState -> CoreM (KureM (Maybe KernelState, a)))
-> (MVar (KureM (AST, a))) -> Msg
Read :: (Map AST KernelState -> IO ()) -> Msg
Delete :: AST -> Msg
Done :: Maybe AST -> Msg
-- | Put a 'KernelState' in the 'ASTMap', returning
-- the 'AST' to which it was assigned.
insertAST :: KernelState -> ASTMap -> (AST, ASTMap)
insertAST ks (ASTMap k m) = (k, ASTMap (succ k) (insert k ks m))
findAST :: AST -> Map AST KernelState -> (String -> b) -> (KernelState -> b) -> b
findAST ast m f = find ast m (f $ "Cannot find syntax tree: " ++ show ast)
-- | Start a HERMIT client by providing an IO callback that takes the
-- initial 'Kernel' and inital 'AST' handle. The callback is only
-- ever called once. The 'Modguts -> CoreM Modguts' function
-- required by GHC Plugins is returned.
hermitKernel :: IORef (Maybe (AST, ASTMap)) -- ^ Global (across passes) AST store.
-> String -- ^ Last GHC pass name
-> (Kernel -> AST -> IO ()) -- ^ Callback
-> ModGuts -> CoreM ModGuts
hermitKernel store lastPass callback modGuts = do
msgMV :: MVar Msg <- liftIO newEmptyMVar
let withAST :: (MonadIO m, MonadCatch m)
=> AST -> (KernelState -> CoreM (KureM (Maybe KernelState, a))) -> m (AST, a)
withAST ast k = do
r <- liftIO $ do
resVar <- newEmptyMVar
putMVar msgMV $ Apply ast k resVar
takeMVar resVar
runKureM return fail r
readOnly :: MonadIO m => (Map AST KernelState -> KureM a) -> m (KureM a)
readOnly f = liftIO $ do
resVar <- newEmptyMVar
putMVar msgMV (Read (runKureM (putMVar resVar . return)
(putMVar resVar . fail) . f))
takeMVar resVar
let kernel :: Kernel
kernel = Kernel
{ resumeK = liftIO . putMVar msgMV . Done . Just
, abortK = liftIO $ putMVar msgMV (Done Nothing)
, applyK = \ rr cm kEnv ast -> liftM fst $
withAST ast $ \ (KernelState lemmas guts _ _) -> do
let handleS hRes = return $ return
(Just (KernelState (hResLemmas hRes) (hResult hRes) (Just ast) (msg cm)), ())
runHM (mkEnv (kEnvChan kEnv) guts lemmas)
handleS
(return . fail)
(applyT rr (topLevelHermitC guts) guts)
, queryK = \ t cm kEnv ast ->
withAST ast $ \ (KernelState lemmas guts _ _) -> do
let handleS hRes
| hResChanged hRes = f (Just (KernelState (hResLemmas hRes) guts (Just ast) (msg cm)), r)
| Always s <- cm = f (Just (KernelState lemmas guts (Just ast) (Just s)), r)
| otherwise = f (Nothing, r) -- pure query, not recorded in AST store
where r = hResult hRes
f = return . return
runHM (mkEnv (kEnvChan kEnv) guts lemmas)
handleS
(return . fail)
(applyT t (topLevelHermitC guts) guts)
, deleteK = liftIO . putMVar msgMV . Delete
, listK = readOnly (\m -> return [ (ast,cm,p) | (ast,KernelState _ _ p cm) <- toList m ])
>>= runKureM return fail
, tellK = \ str ast -> liftM fst $
withAST ast $ \ (KernelState lemmas guts _ _) ->
return $ return (Just $ KernelState lemmas guts (Just ast) (Just str), ())
}
let loop :: ASTMap -> CoreM ModGuts
loop m = do
cmd <- liftIO $ takeMVar msgMV
case cmd of
Apply ast f resVar -> do
kr <- findAST ast (astMap m) (return . fail) f
let handleS (mbKS, r) =
case mbKS of
Nothing -> liftIO (putMVar resVar $ return (ast,r)) >> loop m
Just ks -> let (ast', m') = insertAST ks m in
liftIO (putMVar resVar (return (ast',r))) >> loop m'
handleF str = liftIO (putMVar resVar $ fail str) >> loop m
runKureM handleS handleF kr
Read fn -> liftIO (fn (astMap m)) >> loop m
Delete ast -> loop $ ASTMap (astNext m) $ delete ast (astMap m)
Done mbAST ->
case mbAST of
Nothing ->
abortKernel "Exiting HERMIT and aborting GHC compilation."
Just ast -> do
findAST ast (astMap m)
(\str -> abortKernel $ str ++ ", exiting HERMIT and aborting GHC compilation.")
(\ks -> liftIO (writeIORef store (Just (ast, m))) >> return (ksGuts ks))
-- Get the most recent AST and ASTMap the last HERMIT pass resumed with.
mbS <- liftIO $ readIORef store
(ast0,m) <- case mbS of
Nothing -> return $ insertAST (KernelState empty modGuts Nothing Nothing) emptyASTMap
Just (ast,m) -> do
ls <- findAST ast (astMap m)
(\str -> abortKernel $ str ++ ", exiting HERMIT and aborting GHC compilation.")
(return . ksLemmas)
return $ insertAST (KernelState ls modGuts (Just ast) (Just lastPass)) m
void $ liftIO $ forkIO $ callback kernel ast0
loop m
abortKernel :: String -> CoreM a
abortKernel = throwGhcException . ProgramError
find :: Ord k => k -> Map k v -> b -> (v -> b) -> b
find k m f s = maybe f s (lookup k m)